[Proposal] Disallow implicit conversion between function/closure with a list of parameters and with tuple parameter. Remove function type inconsistency


(Vladimir) #1

I believe this should be done for Swift 3.0 release as this is a *source breaking change* and IMO it is very important to remove the inconsistency mentioned below.

We removed tuple splatting on caller side and IMO we must complete this job to delete the implicit connection between tuple and list of parameters in closures/functions.

Currently we have these "features" :

···

====================================

1. Single tuple as parameter is allowed when list of parameters are required:

let ft1 : (Int,Int) -> Void = { x in print(x.0, x.1)}

(but this causes crash:
let ft2 : (Int,Int) -> Void = { x in print(x) }
)

Opinion: this should not be allowed. Parameter list is required.
`(Int,Int) -> Void` and `((Int,Int)) -> Void` are two different types.

2. Parameter list in closure is allowed when single tuple parameter is required:

typealias IntInt = (Int,Int)
typealias IntIntToVoid = (IntInt) -> Void

let tuple : IntInt = (1,2)

func foo(block: IntIntToVoid) { block(tuple) }

foo { x, y in print(x,y)}
foo { (x, y) in print(x, y)}

Opinion: this should not be allowed. Tuple parameter is required.
`((Int,Int)) -> Void` and `(Int,Int) -> Void` are two different types.
Swift should require this syntax to assign tuple parameter's sub-values to variables in closure: `{ ((x, y)) in ..}`

3. Inconsistent (and just wrong) function type when a list of parameters required(not tuple) :

typealias t1 = (Int, Int) -> Int // clearly here are list of parameters
typealias t2 = ((Int, Int)) -> Int // clearly here is a tuple parameter

print(t1.self) // Prints ((Int, Int)) -> Int why?
print(t2.self) // Prints ((Int, Int)) -> Int
print(t1.self == t2.self) // true

Opinion: `(Int,Int) -> Void` and `((Int,Int)) -> Void` should be two different separate types that can not be implicitly converted to each other. Swift's typesystem should separate these types.

4. If the type is the same, why behavior differs :

let add_list: (Int, Int) -> Int = (+)
let add_tuple: ((Int, Int)) -> Int = (+)

print(add_list.dynamicType == add_tuple.dynamicType) // true

print( add_list(1,2) )
//print( add_list((1,2)) ) // missing argument for parameter #2 in call

//print( add_tuple(1,2) ) // extra argument in call
print( add_tuple((1,2)) )

Proposal:

1. Separate function types with parameter list and a tuple parameter. They should be two separate types.

2. Require this syntax to assign tuple parameter's sub-values to variables in func/closure: `{ ((x, y)) in ..}`, otherwise (i.e. if `{ (x, y) in ..`) treat function/closure as having list of parameters.

3. Disallow implicit conversion between function/closure with a list of parameters and function/closure where single tuple is required.
This will stop confusion and make the language consistent how it deal with tuples and list of parameters in func/closure.

4. It seems like we should keep the ability to explicitly convert one to another as some(many?) code can depend on this current behavior and so we need a way to convert old code to new.


(Saagar Jha) #2

+1. I had this same problem when using map with enumerated-both $1 and
$0.offset worked. I can see how this can be confusing to beginners.

···

On Sat, Jun 25, 2016 at 8:36 AM Vladimir.S via swift-evolution < swift-evolution@swift.org> wrote:

I believe this should be done for Swift 3.0 release as this is a *source
breaking change* and IMO it is very important to remove the inconsistency
mentioned below.

We removed tuple splatting on caller side and IMO we must complete this job
to delete the implicit connection between tuple and list of parameters in
closures/functions.

Currently we have these "features" :

1. Single tuple as parameter is allowed when list of parameters are
required:

let ft1 : (Int,Int) -> Void = { x in print(x.0, x.1)}

(but this causes crash:
let ft2 : (Int,Int) -> Void = { x in print(x) }
)

Opinion: this should not be allowed. Parameter list is required.
`(Int,Int) -> Void` and `((Int,Int)) -> Void` are two different types.

2. Parameter list in closure is allowed when single tuple parameter is
required:

typealias IntInt = (Int,Int)
typealias IntIntToVoid = (IntInt) -> Void

let tuple : IntInt = (1,2)

func foo(block: IntIntToVoid) { block(tuple) }

foo { x, y in print(x,y)}
foo { (x, y) in print(x, y)}

Opinion: this should not be allowed. Tuple parameter is required.
`((Int,Int)) -> Void` and `(Int,Int) -> Void` are two different types.
Swift should require this syntax to assign tuple parameter's sub-values to
variables in closure: `{ ((x, y)) in ..}`

3. Inconsistent (and just wrong) function type when a list of parameters
required(not tuple) :

typealias t1 = (Int, Int) -> Int // clearly here are list of parameters
typealias t2 = ((Int, Int)) -> Int // clearly here is a tuple parameter

print(t1.self) // Prints ((Int, Int)) -> Int why?
print(t2.self) // Prints ((Int, Int)) -> Int
print(t1.self == t2.self) // true

Opinion: `(Int,Int) -> Void` and `((Int,Int)) -> Void` should be two
different separate types that can not be implicitly converted to each
other. Swift's typesystem should separate these types.

4. If the type is the same, why behavior differs :

let add_list: (Int, Int) -> Int = (+)
let add_tuple: ((Int, Int)) -> Int = (+)

print(add_list.dynamicType == add_tuple.dynamicType) // true

print( add_list(1,2) )
//print( add_list((1,2)) ) // missing argument for parameter #2 in call

//print( add_tuple(1,2) ) // extra argument in call
print( add_tuple((1,2)) )

Proposal:

1. Separate function types with parameter list and a tuple parameter. They
should be two separate types.

2. Require this syntax to assign tuple parameter's sub-values to variables
in func/closure: `{ ((x, y)) in ..}`, otherwise (i.e. if `{ (x, y) in ..`)
treat function/closure as having list of parameters.

3. Disallow implicit conversion between function/closure with a list of
parameters and function/closure where single tuple is required.
This will stop confusion and make the language consistent how it deal with
tuples and list of parameters in func/closure.

4. It seems like we should keep the ability to explicitly convert one to
another as some(many?) code can depend on this current behavior and so we
need a way to convert old code to new.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Saagar Jha


(Haravikk) #3

+1 to this; I seem to keep running into cases of this, and it crops up especially when Swift is having difficulty inferring a type, which can be tricky to debug as it is, so I think it's better to just be consistent and explicit, with double brackets for all tuple type closures as proposed.

···

On 25 Jun 2016, at 16:35, Vladimir.S via swift-evolution <swift-evolution@swift.org> wrote:

I believe this should be done for Swift 3.0 release as this is a *source breaking change* and IMO it is very important to remove the inconsistency mentioned below.

We removed tuple splatting on caller side and IMO we must complete this job to delete the implicit connection between tuple and list of parameters in closures/functions.

Currently we have these "features" :

1. Single tuple as parameter is allowed when list of parameters are required:

let ft1 : (Int,Int) -> Void = { x in print(x.0, x.1)}

(but this causes crash:
let ft2 : (Int,Int) -> Void = { x in print(x) }
)

Opinion: this should not be allowed. Parameter list is required.
`(Int,Int) -> Void` and `((Int,Int)) -> Void` are two different types.

2. Parameter list in closure is allowed when single tuple parameter is required:

typealias IntInt = (Int,Int)
typealias IntIntToVoid = (IntInt) -> Void

let tuple : IntInt = (1,2)

func foo(block: IntIntToVoid) { block(tuple) }

foo { x, y in print(x,y)}
foo { (x, y) in print(x, y)}

Opinion: this should not be allowed. Tuple parameter is required.
`((Int,Int)) -> Void` and `(Int,Int) -> Void` are two different types.
Swift should require this syntax to assign tuple parameter's sub-values to variables in closure: `{ ((x, y)) in ..}`

3. Inconsistent (and just wrong) function type when a list of parameters required(not tuple) :

typealias t1 = (Int, Int) -> Int // clearly here are list of parameters
typealias t2 = ((Int, Int)) -> Int // clearly here is a tuple parameter

print(t1.self) // Prints ((Int, Int)) -> Int why?
print(t2.self) // Prints ((Int, Int)) -> Int
print(t1.self == t2.self) // true

Opinion: `(Int,Int) -> Void` and `((Int,Int)) -> Void` should be two different separate types that can not be implicitly converted to each other. Swift's typesystem should separate these types.

4. If the type is the same, why behavior differs :

let add_list: (Int, Int) -> Int = (+)
let add_tuple: ((Int, Int)) -> Int = (+)

print(add_list.dynamicType == add_tuple.dynamicType) // true

print( add_list(1,2) )
//print( add_list((1,2)) ) // missing argument for parameter #2 in call

//print( add_tuple(1,2) ) // extra argument in call
print( add_tuple((1,2)) )

Proposal:

1. Separate function types with parameter list and a tuple parameter. They should be two separate types.

2. Require this syntax to assign tuple parameter's sub-values to variables in func/closure: `{ ((x, y)) in ..}`, otherwise (i.e. if `{ (x, y) in ..`) treat function/closure as having list of parameters.

3. Disallow implicit conversion between function/closure with a list of parameters and function/closure where single tuple is required.
This will stop confusion and make the language consistent how it deal with tuples and list of parameters in func/closure.

4. It seems like we should keep the ability to explicitly convert one to another as some(many?) code can depend on this current behavior and so we need a way to convert old code to new.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Greg Lutz) #4

Vladimir.S via swift-evolution <swift-evolution@...> writes:

Proposal:

1. Separate function types with parameter list and a tuple parameter. They
should be two separate types.

2. Require this syntax to assign tuple parameter's sub-values to variables
in func/closure: `{ ((x, y)) in ..}`, otherwise (i.e. if `{ (x, y) in ..`)
treat function/closure as having list of parameters.

3. Disallow implicit conversion between function/closure with a list of
parameters and function/closure where single tuple is required.
This will stop confusion and make the language consistent how it deal with
tuples and list of parameters in func/closure.

4. It seems like we should keep the ability to explicitly convert one to
another as some(many?) code can depend on this current behavior and so we
need a way to convert old code to new.

+1, at least. Please see my bug report SR-1795 ("An overloaded function
where one instance takes a tuple and the other takes the members of the
tuple will crash in SILGen.") This has been true of every Swift compiler
since at least July 2015. It's a clear consequence of the knots the compiler
gets itself into when the distinction between tuples and parameter lists is
muddy.

BTW, it's unfortunate that, even with this proposal in effect, `{ x, y in
..}` means the same thing as `{ (x, y) in ..}`, whereas `{ ((x, y)) in
..}` is different from those two, but the same as `{ (((x, y))) in ..}`.


(Vladimir) #5

Vladimir.S via swift-evolution <swift-evolution@...> writes:

Proposal:

1. Separate function types with parameter list and a tuple parameter. They
should be two separate types.

2. Require this syntax to assign tuple parameter's sub-values to variables
in func/closure: `{ ((x, y)) in ..}`, otherwise (i.e. if `{ (x, y) in ..`)
treat function/closure as having list of parameters.

3. Disallow implicit conversion between function/closure with a list of
parameters and function/closure where single tuple is required.
This will stop confusion and make the language consistent how it deal with
tuples and list of parameters in func/closure.

4. It seems like we should keep the ability to explicitly convert one to
another as some(many?) code can depend on this current behavior and so we
need a way to convert old code to new.

+1, at least. Please see my bug report SR-1795 ("An overloaded function
where one instance takes a tuple and the other takes the members of the
tuple will crash in SILGen.") This has been true of every Swift compiler
since at least July 2015. It's a clear consequence of the knots the compiler
gets itself into when the distinction between tuples and parameter lists is
muddy.

BTW, it's unfortunate that, even with this proposal in effect, `{ x, y in
..}` means the same thing as `{ (x, y) in ..}`, whereas `{ ((x, y)) in
..}` is different from those two, but the same as `{ (((x, y))) in ..}`.

As was discussed earlier, it seems like most of us agree that we should not require the parenthesis for arguments in closure. But from other side, function with list of parameters has parenthesis - so it is natural that (x, y) in closure should means exactly the same as in function declaration - list of parameters. So, then only ((x, y)) should mean 1 parameter of tuple type.

As for ((((((x, y)))))) I repeatedly suggest to disallow such syntax, but seems like no one care so.. we have what we have.

···

On 05.07.2016 8:39, Greg Lutz via swift-evolution wrote:

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