I ran into a weird tuple destructuring problem in Swift 5.2.
I thought tuple destructuring didn't work inside a closure parameter since some time ago.
And indeed, the following does not work:
let expected: ((Int, Int)) -> Void = { a, b in // does not compile
print(a, b)
}
However, I noticed that all of the below work:
func weird(closure: #escaping ((Int, Int)) -> Void) {
closure((1, 3))
}
// I can understand this
weird { print($0.0 + $0.1) }
weird { a, b in print(a + b) }
// Surprise!
weird { print($0 + $1) }
weird { p in print(p.0 + p.1) }
Did the last two examples worked before Swift 5.2? Is this behavior documented somewhere?
It is related to this proposal, which was once accepted in Swift 4.0.
SE-0110 Distinguish between single-tuple and multiple-argument function types
So, two of your examples (not last two though) caused error in a certain version of Swift, but Swift team found some unacceptable regression in this implementation.
Additional Commentary
And since a certain version of Swift (I do not remember the exact version, it might have been 4.0.x or 4.1), you can pass two argument closure to the parameter of function type taking a single tuple.
And recently, this exception is confirmed to be a part of SE-0110 and if some more modification is needed, a new proposal will be made and go through the usual Swift Evolution process.
Update on SE-0110 and SE-0155
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.
The Swift book may not have clear explanation of this behavior, but it surely is documented.
Related
I just found out there's an implicit cast :
From function taking n parameters (A, B, ...) -> R
To function taking a n-tuple ((A, B, ...)) -> R
Example n°1
func withTuple(_ a: (Int, Int)) { }
func withoutTuple(_ a: Int, _ b: Int) { }
func call(tupleFunction: ((Int, Int)) -> ()) {
tupleFunction((1, 2))
}
call(tupleFunction: withTuple)
call(tupleFunction: withoutTuple) // Magic here
(Valid Swift 4.2 code)
Example n°2
[(1, 2), (3, 3)].map(*) // Magic here
Is this behaviour documented somewhere?
Before Swift 3, one was able to call a function either by explicitly specifying its arguments, or by passing a well-crafted tuple. However this way of calling functions was removed when SE-0029 was implemented.
Basically, the following was possible:
func buildDescription(name: String, age: Int) -> String {
return "Hi, I am \(name), and I am \(age)"
}
buildDescription("John Doe", age: 21)
// or, via a tuple, giving the same result
buildDescription(("John Doe", name: 21))
The Swift forums have this post regarding the above change (emphasis mine):
The proposal has been accepted for Swift 3. We acknowledge that we're removing a useful feature without providing an equally expressive drop-in replacement. However, maintaining this behavior in the type checker is a severe source of implementation complexity, and actively interferes with our plans to solidify the type system.
So it looks like the call-function-by-tuple support was prohibited only at the type checker level, meaning you cannot directly pass tuples to functions, however the internals of the compiler remained the same, which allow indirect passes of tuples, like in the examples from the question.
Background
This is naturally legal:
let closure: (Int, Int) -> () = { print($0 + $1) }
closure(1, 2) // 3
Whereas, since the implementation of evolution proposal
SE-0111: Remove type system significance of function argument labels
in Swift 3, the following is not legal:
let closure: (a: Int, b: Int) -> () = { /* ... */ }
Error: function types may not have argument label a, use _ instead.
Which is expected, as, quoting from SE-0111:
Function types may only be defined in terms of the types of the formal
parameters and the return value.
Curiously, however (and as prompted by the error message above), this is legal:
let closure: (_ a: Int, _ b: Int) -> () = { print($0 + $1) }
closure(1, 2) // 3
However, as far as I can tell, we can't ever make use of a and b above (they shouldn't even be allowed, as they are not part of the types of the parameters?).
Question
Is there any reason for the final code snippet above to be legal? Can we make use or access the "internal argument labels" a and b (given the quote, we shouldn't ...), or is this possibly an oversight in the implementation of SE-0111?
What you're observing is the ability to define "purely cosmetic" parameter labels for closure types. This was accepted as a part of SE-0111, as stated in the rationale:
In response to community feedback, the core team is accepting the proposal with a revision to allow “purely cosmetic” parameter labels in closure types for documentation (as outlined in the alternatives section).
The syntax for these cosmetic parameter labels changed after the proposal to require an argument label of _, in order to make it explicit that the cosmetic labels aren't used at the call-site. This was detailed in an additional commentary:
The specific revision requested by the core team to SE-0111 is that
all “cosmetic” labels should be required to include an API name of _.
For example, this would not be allowed:
var op : (lhs : Int, rhs : Int) -> Int
instead, it should be spelled as:
var op : (_ lhs : Int, _ rhs : Int) -> Int
Although really, in practice, this makes the cosmetic labels fairly useless, as they don't show up at the call-site or even in auto-completion – only at the actual declaration itself. Therefore their intent to be self-documenting is somewhat lost.
The Swift team are aware of this shortcoming, and will be looking to make a purely additive change post-Swift 3 in order to rectify the situation.
Here's the sketch that they proposed (again, in the additional commentary):
First, we extend declaration names for variables, properties, and parameters to allow parameter names as part of their declaration name. For example:
var op(lhs:,rhs:) : (Int, Int) -> Int // variable or property.
x = op(lhs: 1, rhs: 2) // use of the variable or property.
// API name of parameter is “opToUse”, internal name is "op(lhs:,rhs:)”.
func foo(opToUse op(lhs:,rhs:) : (Int, Int) -> Int) {
x = op(lhs: 1, rhs: 2) // use of the parameter
}
foo(opToUse: +) // call of the function
This will restore the ability to express the idea of a closure
parameter that carries labels as part of its declaration, without
requiring parameter labels to be part of the type system (allowing,
e.g. the operator + to be passed into something that requires
parameter labels).
Second, extend the rules for function types to allow parameter API
labels if and only if they are used as the type of a declaration
that allows parameter labels, and interpret them as a sugar form for
providing those labels on the underlying declaration. This means that
the example above could be spelled as:
var op : (lhs: Int, rhs: Int) -> Int // Nice declaration syntax
x = op(lhs: 1, rhs: 2) // Same as above
// API name of parameter is “opToUse”, internal name is "op(lhs:,rhs:)”.
func foo(opToUse op : (lhs: Int, rhs: Int) -> Int) {
x = op(lhs: 1, rhs: 2) // Same as above.
}
foo(opToUse: +) // Same as above.
This proposed solution quite nicely allows the labels to be used at the call-site, allowing for self-documenting parameter labels, while not complicating the type-system with them. Additionally (in most cases) it allows for the expressive syntax of writing the labels next to the parameter types of the closure – which we were used to doing pre-Swift 3.
There have been complaints, not unreasonable, that if you remove the ability to name the parameters, you lose an important aspect of the human communication that tells a future maintainer or user of this code the purpose of these parameters. Well, that ability has been removed as far as external parameter names are concerned. But by leaving it open to sneak past the compiler by using just internal parameter names, we recover at least something of that communication.
However, we do not recover enough of that communication, because the internal parameters don't show up in code completion:
This has been criticized as a flaw in the new regime on this matter, and, in my opinion, rightly so.
So I've been messing around with partial function application i.e.
func partial<A, B, C>(_ #escaping f: (A, B) -> C, _ a: A) -> (_ b: B) -> C {
return { b in f(a, b) }
}
which is cool. I can do let equals3 = partial(==, 3) and everything works as expected
now when I try to make an operator for this in Swift 3 things are getting not so cool
infix operator •
func • <A, B, C>(_ #escaping lhs: (A, B) -> C, _ rhs: A) -> (_ b: B) -> C {
return { b in lhs(rhs, b) }
}
let equals3 = (== • 3) raises compiler message Unary operator cannot be separated from it's operand
so I thought ok, maybe it's getting confused because these two operators == and • are next to each other without declaring any precedence rules (even though in my understanding == shouldn't be considered an operator in this situation)
but still, doing
precedencegroup FunctionApplicationPrecedence {
higherThan: ComparisonPrecedence // the precedencegroup == belongs to
}
infix operator • : FunctionApplicationPrecedence
let equals3 = (== • 3) doesn't compile and raises the same message
am I misunderstanding something here?
So I've been messing around with partial function application
Yeah… that's probably going to go poorly… Swift lacks lots of features you would want to make that work well, and has steadily removed features (like currying syntax) that it even had. This is one of the great "you're fighting Swift" roads that we all walk down at one point or another.
That said, this has an easy fix, not unlike the fix in Haskell. You just have to put parens around the operator so the parser doesn't lose its mind. (It's probably worth opening a JIRA about the diagnostic. You should get a better error.)
let equals3 = ((==) • 3)
Or more simply:
let equals3 = (==) • 3
But oh, Swift will fight you if you keep going down this road. It'll all be roses until it's all SIG11s and expression too complex and "what do you mean there aren't higher-kinded types?"
But it's a fun road until you start throwing things, so curry on.
(If you want to see my latest walk way too far down this road, here's FP in Swift. It... works... kinda. But you fight the compiler the whole way.)
The following example expands upon the example shown in this swift-evolution link, which describes only one parameter per argument list. Any suggestion on how to fix a two parameter argument list?
// Before: (yields warning)
func curried(x: Int)(y: String, z:String) -> Float {
return Float(x) + Float(y)! + Float(z)!
}
// After: (this is not working)
func curried(x: Int) -> (String, String) -> Float {
return {(y: String, z: String) -> Float in
return Float(x) + Float(y)! + Float(z)!
}
}
Xcode 7.3 still reports the second method as "Curried function declaration syntax will be removed in a future version of Swift; use a single parameter list."
Any help appreciated.
Ignore the Xcode 7.3 warning about the second version. If you clean out the build folder, the warning will probably go away. More important, the second version does work — it compiles in Swift 3, which is all that matters.
Is there a proper way to use a variadic parameter list in a closure in Swift?
In swift I notice that I can declare a function which takes a variadic argument list like so
protocol NumberType: Comparable, IntegerLiteralConvertible, IntegerArithmeticType {}
extension Int: NumberType {}
extension SequenceType where Generator.Element: NumberType {
func satisfy(closure:(args: Generator.Element...) -> ()) {
// Do something
closure(args: 1, 2, 3)
}
}
Which builds just fine. When I try to use the function:
[1, 2].satisfy { (args) in
print (args)
}
Xcode manages to auto complete as I would expect, but immediately upon closing the parenthesis after args, all syntax highlighting in Xcode disappears and I see a message "Command failed due to signal: Segmentation Fault: 11", which appears to just mean Xcode is super confused.
For context, I had planned on seeing if Swift could write a function which could return answers based on a variable number of parameters (mapping to the number of for loops required to get a brute force answer). It would be a simple way of testing the answer to a question such as "Given an array of Ints, find all combinations which satisfy the equation a^3 + b^3 = c^3 + d^3" with
let answer = array.satisfy ({ return pow($0, 3) + pow($1, 3) == pow($2, 3) + pow($3, 3) })
against a more optimal solution.
"Return all the 2s" would just be
let answer = array.satisfy ({ return $0 == 2 })
A single for loop
Compiler limitation/bug with argument type inference for single-expression closures
I believe the source of this is a current limitation (/bug) in the compiler w.r.t. inferring the argument types in single-line closures using variadic parameters, see e.g. the following Q&A
Why can't I use .reduce() in a one-liner Swift closure with a variadic, anonymous argument?
A similar issue was also present for inout arguments in Swift 2.1 (but no longer in 2.2), as is explained in the following thread
Inline if statement mutating inout parameter in a void return closure, weird error (Error: type 'Int1' does not conform to protocol 'BooleanType')
Looking at thread 1. as well as attempting to find the described bug flagged in Swift JIRA, however, it seems as if the OP of thread 1. never filed a bug for this, after all. Possibly I just haven't found an existing bug report, but if none exists, one should possibly be filed.
Current workarounds
Possible workarounds, until the compiler's closure argument type inference catches up, are
Extend the closure beyond single-line body
// ...
[1, 2].satisfy { (args) in
() // dummy
print (args) // [1, 2, 3]
}
Or, explicitly include type of args, e.g.
[1, 2].satisfy { (args: Int...) in
print (args) // [1, 2, 3]
}
Note that Generator.Element resolves to Int in this example above.
Current status for Swift 3.0-dev
As mentioned briefly above, curiously enough, this bug
inout: is apparently no longer present in Swift 2.2 or Swift 3.0-dev for inout arguments, w.r.t. the issues described in Q&A 2. as linked to above
it was possibly fixed as bug [SR-7] was resolved (-> Swift 2.2)
however, seems to be regression 2.2->3.0-dev, w.r.t. type inference for inout arguments, as reported in bug report [SR-892]. E.g. the following snippet works in Swift 2.2, but not in 3.0-dev (minimally modified snipper from bug report [SR-7])
func f(inout a: Int) {}
let g = { x in f(&x) } // OK 2.2, crashes 3.0-dev
variadic: is still present in Swift 2.2 as well as Swift 3.0-dev for variadic arguments (this thread and Q&A 1. above).
a more condensed example of the bug:
let a: (Int...) -> () = { (args) in print(args) } // bug: crashes
let b: (Int...) -> () = { (args: Int...) in print(args) } // explicitly state argument type, OK
let c: (Int...) -> () = { (args) in (); print(args) } // extend to more than single line closure, OK
(For Swift 3.0-dev, tested using the IBM Swift Sandbox running Swift 3.0-dev.