SE-0110: Distinguish between single-tuple and multiple-argument function types
* Proposal: [SE-0110](0110-distinguish-single-tuple-arg.md) * Authors: Vladimir S., [Austin Zheng](https://github.com/austinzheng) * Review Manager: [Chris Lattner](https://github.com/lattner) * Status: **Implemented** * Decision Notes: [Rationale](https://forums.swift.org/t/accepted-se-0110-distinguish-between-single-tuple-and-multiple-argument-function-types/3305), [Additional Commentary](https://forums.swift.org/t/core-team-addressing-the-se-0110-usability-regression-in-swift-4/6147) * Bug: [SR-2008](https://bugs.swift.org/browse/SR-2008) * Previous Revision: [Originally Accepted Proposal](https://github.com/swiftlang/swift-evolution/blob/9e44932452e1daead98f2bc2e58711eb489e9751/proposals/0110-distingish-single-tuple-arg.md)
Introduction
Swift's type system should properly distinguish between functions that take one tuple argument, and functions that take multiple arguments.
Discussion: pre-proposal
Motivation
Right now, the following is possible:
let fn1 : (Int, Int) -> Void = { x in
// The type of x is the tuple (Int, Int).
// ...
}
let fn2 : (Int, Int) -> Void = { x, y in
// The type of x is Int, the type of y is Int.
// ...
}A variable of function type where there exist n parameters (where n > 1) can be assigned a value (whether it be a named function, a closure literal, or other acceptable value) which either takes in n parameters, or one tuple containing n elements. This seems to be an artifact of the tuple splat behavior removed in SE-0029.
The current behavior violates the principle of least surprise and weakens type safety, and should be changed.
Proposed solution
We propose that this behavior should be fixed in the following ways:
- A function type declared with n parameters (n > 1) can only be satisfied by a function value which takes in n parameters. In the above example, only the
fn2expression would be considered valid.
- To declare a function type with one tuple parameter containing n elements (where n > 1), the function type's argument list must be enclosed by double parentheses:
``swift let a : ((Int, Int, Int)) -> Int = { x in return x.0 + x.1 + x.2 } ``
We understand that this may be a departure from the current convention that a set of parentheses enclosing a single object are considered semantically meaningless, but it is the most natural way to differentiate between the two situations described above and would be a clearly-delineated one-time-only exception.
Existing Swift code widely takes advantage of the ability to pass a multi-parameter closure or function value to a higher-order function that operates on tuples, particularly with collection operations:
zip([1, 2, 3], [3, 2, 1]).filter(<) // => [(1, 3)]
zip([1, 2, 3], [3, 2, 1]).map(+) // => [4, 4, 4]Without the implicit conversion, this requires invasive changes to explicitly destructure the tuple argument. In order to gain most of the type system benefits of distinguishing single-tuple-argument functions from multiple-argument functions, while maintaining the fluidity of functional code like the above, arguments of type (T, U, ...) -> V in call expressions are allowed to be converted to parameters of the corresponding single-tuple parameter type ((T, U, ...)) -> V, so the two examples above will continue to be accepted.
Impact on existing code
Minor changes to user code may be required if this proposal is accepted.
Alternatives considered
Don't make this change.
Revision history
The original proposal as reviewed did not include the special-case conversion from (T, U, ...) -> V to ((T, U, ...)) -> V for function arguments. In response to community feedback, this conversion was added as part of the Core Team's acceptance of the proposal.