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.
Related
I'm wondering if Swift has a way to let you pass in a specific overload of a function as an argument to a second function that takes a closure with a matching signature, based on type, but without explicitly creating a closure yourself.
Note: This is not a problem I'm trying to solve. It's just a curiosity about the language based on something I ran into when creating an extension to a struct that suddenly broke the compile. It was easily addressed with a closure, like below. It just had me wondering if there was another way to solve it.
Here's code showing what I mean. Let's start with this struct Foo...
struct Foo {
let value: Int
}
Once defined, we can use it as part of a mapping operation via a closure, like so...
let values = [1, 2, 3, 4]
let foos = values.map{ Foo(value: $0) }
However, since the initializer itself already matches the signature of the closure argument type-wise, you can skip the manually-created closure entirely and pass in the function directly, like so...
let values = [1, 2, 3, 4]
let foos = values.map(Foo.init)
What's interesting to note is Swift's compiler finds that match based only on the type of the argument to init, not it's label, which was required earlier in the closure version.
What I discovered is by defining the following extension somewhere in code-scope view of the values.map(Foo.init) call site...
extension Foo {
init(valueToDouble value: Int) { self.value = value * 2 }
}
...that call site suddenly gets flagged as ambiguous and it breaks the compile. This is because even though the labels are different, the argument type--Int in this example--is the same and it doesn't know which one to use.
Now again, this is easily solved with a simple closure, like so...
// Original init
let foos = values.map{ Foo(value: $0) }
// or init in the extension
let foos = values.map{ Foo(valueToDouble: $0) }
I'm just wondering if the Swift compiler has some 'magic sauce' that lets me do something like this...
let foos = values.map(Foo.init(valueToDouble:))
...which obviously doesn't work. :)
So is there anything like that, or is the closure-based version the (only) way to go?
Update
Well, nothing "obvious" about it being wrong because what was wrong is I had a typo (now fixed) and the above syntax does in fact work! Thanks, Itai! :)
In Swift, the "base name" of a method is the method name without any arguments:
Foo.init(x:y:z:) → Foo.init
Foo.bar(_:) → Foo.bar
Foo.baz(baz:) → Foo.baz
Foo.quux() → Foo.quux
When referring to a method by name (rather than calling it), Swift will allow you to refer to it by its base name, so long as the usage is not ambiguous:
struct Foo {
func f(intValue: Int) {}
}
let f = Foo().f
However, when there are multiple methods with the same base name, you can run into situations with ambiguity:
struct Foo {
func f(intValue: Int) {}
func f(stringValue: String) {}
}
let f = Foo().f // error: ambiguous use of 'f'
When this happens, you can either:
Use explicit typing to help disambiguate the methods, if possible:
let f1: (Int) -> Void = Foo().f
let f2: (String) -> Void = Foo().f
Or,
Refer to the method by its fully-qualified name (name with parameter names included):
let f1 = Foo().f(intValue:)
let f2 = Foo().f(stringValue:)
In your case, because both methods have the same type signature, you can't use approach (1) to disambiguate between the calls, and will have to resort to (2).
The issue you ran into is that the parameter name you were using was slightly off:
// ❌ Foo.init(doubleValue:)
// ✅ Foo.init(valueToDouble:)
let foos = values.map(Foo.init(valueToDouble:)) // ✅
This does work, and will work as a shorthand instead of having to call the method directly inside of a closure argument.
This code works fine. It iterates my array of one Int! and prints its magnitude.
import Foundation
let x : Int! = 1
[x].forEach {i in
print(i.magnitude)
}
Output:
1
Presumably, i in the loop body is an Int or an Int!, and indeed if I ask Xcode for "quick help" on forEach it reports:
func forEach(_ body: (Int) throws -> Void) rethrows
However, if perform two statements in my forEach body, instead it fails to compile, complaining that I need to unwrap i which now has the optional type Int?.
import Foundation
let x : Int! = 1
[x].forEach {i in
print(i.magnitude)
print(i.magnitude)
}
Compile error:
Value of optional type 'Int?' must be unwrapped to refer to member 'magnitude' of wrapped base type 'Int'
And if I ask for "quick help" now I get:
func forEach(_ body: (Int?) throws -> Void) rethrows
How on earth does the number of statements I place in my loop body manage to affect the type of the loop variable?
Basically, you've elicited an edge case of an edge case. You've combined two things that are the work of the devil:
Implicitly unwrapped Optionals
Implicit type inference of closures, along with the fact that
Implicit type inference of closures works differently when the closure consists of one line (this is where the "How on earth does the number of statements" comes in)
You should try to avoid both of those; your code will be cleaner and will compile a lot faster. Indeed, implicit type inference of anything other than a single literal, like a string, Int, or Double, is a huge drag on compilation times.
I won't pretend to imitate the compiler's reasoning; I'll just show you an actual solution (other than just not using an IUO in the first place):
[x].forEach {(i:Int) in
print(i.magnitude)
print(i.magnitude)
}
Our Int type is legal because we take advantage of the single "get out of jail free" card saying that an implicitly unwrapped Optional can be used directly where the unwrapped type itself is expected. And by explicitly stating the type, we clear up the compiler's doubts.
(I say "directly" because implicit unwrappedness of an Optional is not propagated thru passing and assignment. That is why in your second example you discovered Int?, not Int, being passed into the closure.)
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.
I'm confused by function overload resolution with closures in Swift 3.
For example, in the code:
func f<T>(_ a: T) {
print("Wide")
}
func f(_ a: (Int)->(Int)) {
print("Narrow")
}
f({(a: Int) -> Int in return a + 1})
I expect Narrow, not Wide, to be printed to the console. Can anyone explain why the more specific overload gets chosen for non-closure arguments but not for closures or is this a compiler bug?
Swift 2 exhibited the expected behavior.
This is probably due to the change in the default "escaping" behaviour for closure parameters.
If you change the specific function to:
func f(_ a:#escaping (Int)->Int)
{
print("Narrow")
}
it will print "Narrow" as expected (this is the same change that you probably had to make in several other places that were more obvious)
func makeIncrementer() -> (Int -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
above is a simple example code for Function as first-class type in Swift
now, when i call the call the function in the following way:
var increment = makeIncrementer()
increment(7)
it perfectly gives the answer
But out of curiosity i tried the direct approach i.e.
makeIncrementer(7) // error
and it gives an error
why is it so???
P.S. I am a beginner in Swift
The call makeIncrementer() returns the function, so to call it you pass the parameter in a second set of parentheses:
makeIncrementer()(7)
The error message is given because Swift interprets makeIncrementer(7) as 7 being passed to makeIncrementer which doesn't take any parameters. Hopefully Swift error messages are made more friendly in the future. While technically correct, the error message given leads to a lot of confusion.