[Pitch] Require tuple conversions to be explicit when labels don't match


(Jacob Bandes-Storch) #1

There was some previous discussion under "[Discussion] Enforce argument
labels on tuples
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/14910>".

Halfway through the thread, Haravikk clearly stated the key point:

I think the important thing to remember is that the label check is
intended to prevent cases like this:
let a:(left:Int, right:Int) = (1, 2)
var b:(right:Int, left:Int) = a
While the two tuples are compatible by type, the meaning of the values
may differ due to the different labels; in this case the values are
represented in a different order that a developer should have to explicitly
reverse to ensure they aren’t making a mistake, or they could represent
radically different concepts altogether.

I agree there's a potential for confusion here, and I suggest we should add
an error (or warning) imploring the user to make the conversion explicit,
when the source tuple is labeled:

    func foo() -> (left: Int, right: Int) { return (3, 4) }

    let (left: a, right: b) = foo() // ok, labels match

    var x = 0, y = 0
    (left: x, right: y) = (1, 3) // ok, source is unlabeled

    let (c, d) = foo()
    // error: conversion between tuple types '(left: Int, right: Int)' and
'(Int, Int)' requires explicit 'as' operator
    // suggested fix: "let (c, d) = foo() as (Int, Int)"

    let (right: e, left: f) = foo()
    // error: conversion between tuple types '(left: Int, right: Int)' and
'(right: Int, left: Int)' requires explicit 'as' operator
    // suggested fix: "let (right: e, left: f) = foo() as (right: Int,
left: Int)"

Thoughts?

Jacob

···

On Thu, Apr 21, 2016 at 12:14 AM, Haravikk via swift-evolution < swift-evolution@swift.org> wrote:


(Xiaodi Wu) #2

A sensible solution, IMO. Error with Fix-It when attempting to convert
implicitly between tuples with mismatched labels.

···

On Mon, May 9, 2016 at 01:47 Jacob Bandes-Storch via swift-evolution < swift-evolution@swift.org> wrote:

There was some previous discussion under "[Discussion] Enforce argument
labels on tuples
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/14910>".

Halfway through the thread, Haravikk clearly stated the key point:

On Thu, Apr 21, 2016 at 12:14 AM, Haravikk via swift-evolution < > swift-evolution@swift.org> wrote:

I think the important thing to remember is that the label check is
intended to prevent cases like this:
let a:(left:Int, right:Int) = (1, 2)
var b:(right:Int, left:Int) = a
While the two tuples are compatible by type, the meaning of the values
may differ due to the different labels; in this case the values are
represented in a different order that a developer should have to explicitly
reverse to ensure they aren’t making a mistake, or they could represent
radically different concepts altogether.

I agree there's a potential for confusion here, and I suggest we should
add an error (or warning) imploring the user to make the conversion
explicit, when the source tuple is labeled:

    func foo() -> (left: Int, right: Int) { return (3, 4) }

    let (left: a, right: b) = foo() // ok, labels match

    var x = 0, y = 0
    (left: x, right: y) = (1, 3) // ok, source is unlabeled

    let (c, d) = foo()
    // error: conversion between tuple types '(left: Int, right: Int)' and
'(Int, Int)' requires explicit 'as' operator
    // suggested fix: "let (c, d) = foo() as (Int, Int)"

    let (right: e, left: f) = foo()
    // error: conversion between tuple types '(left: Int, right: Int)' and
'(right: Int, left: Int)' requires explicit 'as' operator
    // suggested fix: "let (right: e, left: f) = foo() as (right: Int,
left: Int)"

Thoughts?

Jacob
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jacob Bandes-Storch) #3

... and one might also want to require labels when passing values *to* a
labeled tuple:

    func foo() -> (left: Int, right: Int) {
        return (3, 4) // error: conversion between tuple types '(Int,
Int)' and '(left: Int, right: Int)' requires explicit 'as' operator
    }

I've personally been bitten by a typo of this sort (mistakenly swapping the
values) before.
Jacob

···

On Mon, May 9, 2016 at 12:23 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

A sensible solution, IMO. Error with Fix-It when attempting to convert
implicitly between tuples with mismatched labels.

On Mon, May 9, 2016 at 01:47 Jacob Bandes-Storch via swift-evolution < > swift-evolution@swift.org> wrote:

There was some previous discussion under "[Discussion] Enforce argument
labels on tuples
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/14910>".

Halfway through the thread, Haravikk clearly stated the key point:

On Thu, Apr 21, 2016 at 12:14 AM, Haravikk via swift-evolution < >> swift-evolution@swift.org> wrote:

I think the important thing to remember is that the label check is
intended to prevent cases like this:
let a:(left:Int, right:Int) = (1, 2)
var b:(right:Int, left:Int) = a
While the two tuples are compatible by type, the meaning of the values
may differ due to the different labels; in this case the values are
represented in a different order that a developer should have to explicitly
reverse to ensure they aren’t making a mistake, or they could represent
radically different concepts altogether.

I agree there's a potential for confusion here, and I suggest we should
add an error (or warning) imploring the user to make the conversion
explicit, when the source tuple is labeled:

    func foo() -> (left: Int, right: Int) { return (3, 4) }

    let (left: a, right: b) = foo() // ok, labels match

    var x = 0, y = 0
    (left: x, right: y) = (1, 3) // ok, source is unlabeled

    let (c, d) = foo()
    // error: conversion between tuple types '(left: Int, right: Int)'
and '(Int, Int)' requires explicit 'as' operator
    // suggested fix: "let (c, d) = foo() as (Int, Int)"

    let (right: e, left: f) = foo()
    // error: conversion between tuple types '(left: Int, right: Int)'
and '(right: Int, left: Int)' requires explicit 'as' operator
    // suggested fix: "let (right: e, left: f) = foo() as (right: Int,
left: Int)"

Thoughts?

Jacob
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #4

Hmm, not as sure about that one. To my mind it's a clear expression of
intent there. You're saying you know what the labels are and you're
choosing not to repeat them. I think it should be on you if you express
that intent and you're just plain wrong.

···

On Mon, May 9, 2016 at 2:32 AM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

... and one might also want to require labels when passing values *to* a
labeled tuple:

    func foo() -> (left: Int, right: Int) {
        return (3, 4) // error: conversion between tuple types '(Int,
Int)' and '(left: Int, right: Int)' requires explicit 'as' operator
    }

I've personally been bitten by a typo of this sort (mistakenly swapping
the values) before.
Jacob

On Mon, May 9, 2016 at 12:23 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

A sensible solution, IMO. Error with Fix-It when attempting to convert
implicitly between tuples with mismatched labels.

On Mon, May 9, 2016 at 01:47 Jacob Bandes-Storch via swift-evolution < >> swift-evolution@swift.org> wrote:

There was some previous discussion under "[Discussion] Enforce argument
labels on tuples
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/14910>".

Halfway through the thread, Haravikk clearly stated the key point:

On Thu, Apr 21, 2016 at 12:14 AM, Haravikk via swift-evolution < >>> swift-evolution@swift.org> wrote:

I think the important thing to remember is that the label check is
intended to prevent cases like this:
let a:(left:Int, right:Int) = (1, 2)
var b:(right:Int, left:Int) = a
While the two tuples are compatible by type, the meaning of the values
may differ due to the different labels; in this case the values are
represented in a different order that a developer should have to explicitly
reverse to ensure they aren’t making a mistake, or they could represent
radically different concepts altogether.

I agree there's a potential for confusion here, and I suggest we should
add an error (or warning) imploring the user to make the conversion
explicit, when the source tuple is labeled:

    func foo() -> (left: Int, right: Int) { return (3, 4) }

    let (left: a, right: b) = foo() // ok, labels match

    var x = 0, y = 0
    (left: x, right: y) = (1, 3) // ok, source is unlabeled

    let (c, d) = foo()
    // error: conversion between tuple types '(left: Int, right: Int)'
and '(Int, Int)' requires explicit 'as' operator
    // suggested fix: "let (c, d) = foo() as (Int, Int)"

    let (right: e, left: f) = foo()
    // error: conversion between tuple types '(left: Int, right: Int)'
and '(right: Int, left: Int)' requires explicit 'as' operator
    // suggested fix: "let (right: e, left: f) = foo() as (right: Int,
left: Int)"

Thoughts?

Jacob
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #5

Also--I didn't read this carefully enough in your initial example--I
disagree that either (left: Int, right: Int) to (Int, Int) or vice versa
should require explicit casting, and I think previous conversations on the
topic showed that at least some on the list felt the same way.

Mismatched labels, yes, because IMO it's alarming without a more explicit
indication of intent to assume that a user intends to swap labels going
from (left: Int, right: Int) to (right: Int, left: Int). But to work around
that restriction, I should be able to erase labels and affix new ones
without using "as", since no unintentional label swapping can occur in
either direction.

···

On Mon, May 9, 2016 at 2:57 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Hmm, not as sure about that one. To my mind it's a clear expression of
intent there. You're saying you know what the labels are and you're
choosing not to repeat them. I think it should be on you if you express
that intent and you're just plain wrong.

On Mon, May 9, 2016 at 2:32 AM, Jacob Bandes-Storch <jtbandes@gmail.com> > wrote:

... and one might also want to require labels when passing values *to* a
labeled tuple:

    func foo() -> (left: Int, right: Int) {
        return (3, 4) // error: conversion between tuple types '(Int,
Int)' and '(left: Int, right: Int)' requires explicit 'as' operator
    }

I've personally been bitten by a typo of this sort (mistakenly swapping
the values) before.
Jacob

On Mon, May 9, 2016 at 12:23 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

A sensible solution, IMO. Error with Fix-It when attempting to convert
implicitly between tuples with mismatched labels.

On Mon, May 9, 2016 at 01:47 Jacob Bandes-Storch via swift-evolution < >>> swift-evolution@swift.org> wrote:

There was some previous discussion under "[Discussion] Enforce
argument labels on tuples
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/14910>".

Halfway through the thread, Haravikk clearly stated the key point:

On Thu, Apr 21, 2016 at 12:14 AM, Haravikk via swift-evolution < >>>> swift-evolution@swift.org> wrote:

I think the important thing to remember is that the label check is
intended to prevent cases like this:
let a:(left:Int, right:Int) = (1, 2)
var b:(right:Int, left:Int) = a
While the two tuples are compatible by type, the meaning of the
values may differ due to the different labels; in this case the
values are represented in a different order that a developer should have to
explicitly reverse to ensure they aren’t making a mistake, or they could
represent radically different concepts altogether.

I agree there's a potential for confusion here, and I suggest we should
add an error (or warning) imploring the user to make the conversion
explicit, when the source tuple is labeled:

    func foo() -> (left: Int, right: Int) { return (3, 4) }

    let (left: a, right: b) = foo() // ok, labels match

    var x = 0, y = 0
    (left: x, right: y) = (1, 3) // ok, source is unlabeled

    let (c, d) = foo()
    // error: conversion between tuple types '(left: Int, right: Int)'
and '(Int, Int)' requires explicit 'as' operator
    // suggested fix: "let (c, d) = foo() as (Int, Int)"

    let (right: e, left: f) = foo()
    // error: conversion between tuple types '(left: Int, right: Int)'
and '(right: Int, left: Int)' requires explicit 'as' operator
    // suggested fix: "let (right: e, left: f) = foo() as (right: Int,
left: Int)"

Thoughts?

Jacob
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #6

Two more points to be made:

First, there are types where current behavior allowing implicit erasure and
affixing of labels is a definite win. Consider:

typealias CartesianCoordinate = (x: Int, y: Int)
let c: CartesianCoordinate = (0, 1)
print(c.x)

typealias PolarCoordinate = (r: Double, theta: Double)
let p: PolarCoordinate = (0.0, M_PI)
print(p.theta)

Second, your examples involving pattern matching are not correct. Since
it's a tuple *pattern*, you'll find that `let (right: e, left: f) = (left:
1, right: 2)` has the same effect as writing `let (left: f, right: e) =
(left: 1, right: 2)`. Which is to say, left goes with left and right goes
with right irrespective of the order of labels on the LHS.

···

On Mon, May 9, 2016 at 4:00 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Also--I didn't read this carefully enough in your initial example--I
disagree that either (left: Int, right: Int) to (Int, Int) or vice versa
should require explicit casting, and I think previous conversations on the
topic showed that at least some on the list felt the same way.

Mismatched labels, yes, because IMO it's alarming without a more explicit
indication of intent to assume that a user intends to swap labels going
from (left: Int, right: Int) to (right: Int, left: Int). But to work around
that restriction, I should be able to erase labels and affix new ones
without using "as", since no unintentional label swapping can occur in
either direction.

On Mon, May 9, 2016 at 2:57 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Hmm, not as sure about that one. To my mind it's a clear expression of
intent there. You're saying you know what the labels are and you're
choosing not to repeat them. I think it should be on you if you express
that intent and you're just plain wrong.

On Mon, May 9, 2016 at 2:32 AM, Jacob Bandes-Storch <jtbandes@gmail.com> >> wrote:

... and one might also want to require labels when passing values *to* a
labeled tuple:

    func foo() -> (left: Int, right: Int) {
        return (3, 4) // error: conversion between tuple types '(Int,
Int)' and '(left: Int, right: Int)' requires explicit 'as' operator
    }

I've personally been bitten by a typo of this sort (mistakenly swapping
the values) before.
Jacob

On Mon, May 9, 2016 at 12:23 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

A sensible solution, IMO. Error with Fix-It when attempting to convert
implicitly between tuples with mismatched labels.

On Mon, May 9, 2016 at 01:47 Jacob Bandes-Storch via swift-evolution < >>>> swift-evolution@swift.org> wrote:

There was some previous discussion under "[Discussion] Enforce
argument labels on tuples
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/14910>".

Halfway through the thread, Haravikk clearly stated the key point:

On Thu, Apr 21, 2016 at 12:14 AM, Haravikk via swift-evolution < >>>>> swift-evolution@swift.org> wrote:

I think the important thing to remember is that the label check is
intended to prevent cases like this:
let a:(left:Int, right:Int) = (1, 2)
var b:(right:Int, left:Int) = a
While the two tuples are compatible by type, the meaning of the
values may differ due to the different labels; in this case the
values are represented in a different order that a developer should have to
explicitly reverse to ensure they aren’t making a mistake, or they could
represent radically different concepts altogether.

I agree there's a potential for confusion here, and I suggest we
should add an error (or warning) imploring the user to make the conversion
explicit, when the source tuple is labeled:

    func foo() -> (left: Int, right: Int) { return (3, 4) }

    let (left: a, right: b) = foo() // ok, labels match

    var x = 0, y = 0
    (left: x, right: y) = (1, 3) // ok, source is unlabeled

    let (c, d) = foo()
    // error: conversion between tuple types '(left: Int, right: Int)'
and '(Int, Int)' requires explicit 'as' operator
    // suggested fix: "let (c, d) = foo() as (Int, Int)"

    let (right: e, left: f) = foo()
    // error: conversion between tuple types '(left: Int, right: Int)'
and '(right: Int, left: Int)' requires explicit 'as' operator
    // suggested fix: "let (right: e, left: f) = foo() as (right: Int,
left: Int)"

Thoughts?

Jacob
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #7

Hmm. I'm also having second thoughts about that Fix-It. I wonder if the
"fix" that's automatically provided is little more than "press here to make
the red thingy go away and do nothing else."

The way I see it, if the error is restricted to mismatched labels, then the
presumption is that someone got the order of the labels wrong. So the error
is basically: "Hey, we think you accidentally swapped your labels. Do you
want us to fix it?" And the default fix, which some people are guaranteed
to choose without even reading the error very carefully, shouldn't be
"Leave the labels swapped even though it looks wrong, and just rewrite that
line so the error disappears!"

···

On Mon, May 9, 2016 at 05:09 Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Two more points to be made:

First, there are types where current behavior allowing implicit erasure
and affixing of labels is a definite win. Consider:

typealias CartesianCoordinate = (x: Int, y: Int)
let c: CartesianCoordinate = (0, 1)
print(c.x)

typealias PolarCoordinate = (r: Double, theta: Double)
let p: PolarCoordinate = (0.0, M_PI)
print(p.theta)

Second, your examples involving pattern matching are not correct. Since
it's a tuple *pattern*, you'll find that `let (right: e, left: f) = (left:
1, right: 2)` has the same effect as writing `let (left: f, right: e) =
(left: 1, right: 2)`. Which is to say, left goes with left and right goes
with right irrespective of the order of labels on the LHS.

On Mon, May 9, 2016 at 4:00 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Also--I didn't read this carefully enough in your initial example--I
disagree that either (left: Int, right: Int) to (Int, Int) or vice versa
should require explicit casting, and I think previous conversations on the
topic showed that at least some on the list felt the same way.

Mismatched labels, yes, because IMO it's alarming without a more explicit
indication of intent to assume that a user intends to swap labels going
from (left: Int, right: Int) to (right: Int, left: Int). But to work around
that restriction, I should be able to erase labels and affix new ones
without using "as", since no unintentional label swapping can occur in
either direction.

On Mon, May 9, 2016 at 2:57 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Hmm, not as sure about that one. To my mind it's a clear expression of
intent there. You're saying you know what the labels are and you're
choosing not to repeat them. I think it should be on you if you express
that intent and you're just plain wrong.

On Mon, May 9, 2016 at 2:32 AM, Jacob Bandes-Storch <jtbandes@gmail.com> >>> wrote:

... and one might also want to require labels when passing values *to*
a labeled tuple:

    func foo() -> (left: Int, right: Int) {
        return (3, 4) // error: conversion between tuple types '(Int,
Int)' and '(left: Int, right: Int)' requires explicit 'as' operator
    }

I've personally been bitten by a typo of this sort (mistakenly swapping
the values) before.
Jacob

On Mon, May 9, 2016 at 12:23 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

A sensible solution, IMO. Error with Fix-It when attempting to convert
implicitly between tuples with mismatched labels.

On Mon, May 9, 2016 at 01:47 Jacob Bandes-Storch via swift-evolution < >>>>> swift-evolution@swift.org> wrote:

There was some previous discussion under "[Discussion] Enforce
argument labels on tuples
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/14910>".

Halfway through the thread, Haravikk clearly stated the key point:

On Thu, Apr 21, 2016 at 12:14 AM, Haravikk via swift-evolution < >>>>>> swift-evolution@swift.org> wrote:

I think the important thing to remember is that the label check is
intended to prevent cases like this:
let a:(left:Int, right:Int) = (1, 2)
var b:(right:Int, left:Int) = a
While the two tuples are compatible by type, the meaning of the
values may differ due to the different labels; in this case the
values are represented in a different order that a developer should have to
explicitly reverse to ensure they aren’t making a mistake, or they could
represent radically different concepts altogether.

I agree there's a potential for confusion here, and I suggest we
should add an error (or warning) imploring the user to make the conversion
explicit, when the source tuple is labeled:

    func foo() -> (left: Int, right: Int) { return (3, 4) }

    let (left: a, right: b) = foo() // ok, labels match

    var x = 0, y = 0
    (left: x, right: y) = (1, 3) // ok, source is unlabeled

    let (c, d) = foo()
    // error: conversion between tuple types '(left: Int, right:
Int)' and '(Int, Int)' requires explicit 'as' operator
    // suggested fix: "let (c, d) = foo() as (Int, Int)"

    let (right: e, left: f) = foo()
    // error: conversion between tuple types '(left: Int, right:
Int)' and '(right: Int, left: Int)' requires explicit 'as' operator
    // suggested fix: "let (right: e, left: f) = foo() as (right:
Int, left: Int)"

Thoughts?

Jacob
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution