Swift 3.0 Closure Expression: What If The Variadic Parameters Not at The Last Place in The Parameters List

How to use a variadic closure in swift?

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

  1. 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


  1. 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.

Swift arguments not allowed when function is assigned to a variable?

  1. Yes.
  2. See SE-0111 - Remove type system significance of function argument labels.

Example:

func foo(bar: Int, baz: String) {
print(bar, baz)
}

foo(bar: 123, baz: "abc") // Valid, prints: 123 abc

let x = foo //Inferred type: (Int, String) -> Void
x(123, "abc") // Valid, prints: 123 abc

let y: (bar: Int, baz: String) -> Void = foo // Invalid
// ERROR at line 10, col 9: function types cannot have argument label 'bar'; use '_' instead
// let y: (bar: Int, baz: String) -> Void
// ^
// _
// ERROR at line 10, col 19: function types cannot have argument label 'baz'; use '_' instead
// let y: (bar: Int, baz: String) -> Void
// ^
// _
y(bar: 123, baz: "abc") // Invalid
// ERROR at line 19, col 2: extraneous argument labels 'bar:baz:' in call
// y(bar: 123, baz: "abc") // 123 abc
// ^~~~~~ ~~~~~

why swift function with variadic parameters can not receive an array as argument

Overload the function definition...

func test(ids : Int...) {
print("\(ids.count) rx as variadic")
}
func test(idArr : [Int]) {
print("\(idArr.count) rx as array")
}
//call function test like this now succeeds
test([1,3])
//... as does this
test(1,3)

// Output:
// "2 rx as array"
// "2 rx as variadic"

Of course, to avoid duplicate code, the variadic version should just call the array version:

func test(ids : Int...) {
print("\(ids.count) rx as variadic")
test(ids, directCall: false)
}
func test(idArr : [Int], directCall: Bool = true) {
// Optional directCall allows us to know who called...
if directCall {
print("\(idArr.count) rx as array")
}
print("Do something useful...")
}

//call function test like this now succeeds
test([1,3])
//... as does this
test(1,3)

// Output:
// 2 rx as array
// Do something useful...
// 2 rx as variadic
// Do something useful...

Swift Variadic Closures Syntax?

Try to specify parameter type and return type in closure to helps compiler to understand what value it should take and return. Also, you have a mistake in for loop. The interval should be like this 0 ..< values.count:

func isAllAbove(lower: Double) -> (Double...) -> Bool {
return { (values: Double...) -> Bool in
var conditions: [Bool] = []
for i in 0 ..< values.count {
conditions.append(lower < values[i])
}
return !conditions.contains(false)
}
}

let allAbove = isAllAbove(lower: 2)
print(allAbove(1, 2, 3)) // false

Also, you can write it almost in 1 line of code:

let lower = 2
let isAllAbove = ![1, 2, 3].contains { $0 < lower }
print(isAllAbove1) // false

Assign print function to a variable

I believe you've found a known bug (or limitation) of the Swift compiler.

The built-in print function has three parameters. Parameter #1 is unlabeled and is type String ... (variadic). Parameter #2 has label separator, is type String, and has a default value. Parameter #3 has label terminator, is type String, and has a default value. Since parameters #2 and #3 have default values, you can call it like print("hello").

Closures (like myPrint) cannot use labeled parameters. So myPrint takes three unlabeled parameters. Parameter #1 is type String ... (variadic). Parameter #2 is type String. Parameter #3 is also type String. Closure parameters cannot have default values, so myPrint requires all three parameters. You cannot call it like myPrint("hello"), because you haven't passed anything for parameters #2 and #3 and they do not (and cannot) have default values.

In Swift, a variadic parameter (like parameter #1 of print and myPrint) consumes all arguments until in reaches a label. If you try to call myPrint("hello", " ", "\n"), all three arguments will be assigned to parameter #1, and nothing will be assigned to parameters #2 and #3.

Since you cannot have labeled parameters in a closure, there is no way to call your myPrint closure, because there is no way to pass values for parameters #2 and #3.

This is a known limitation. See SR-2475 and SR-494.

How do you document the parameters of a function's closure parameter in Swift 3?

As far as I know, you can only document the closure parameters if you label them:

/// Calls bar with "Hello, world"
/// - parameter bar: A closure to call
/// - parameter theString: A string to use
func foo(bar: (theString: String) -> Void) {
bar(theString: "Hello, world")
}

This is less than ideal: it forces you to use an argument label when you call the closure, and if there are naming conflicts, there seems no way to distinguish between the two.

Edit: As @Arnaud pointed out, you can use _ to prevent having to use the parameter label when calling the closure:

/// Calls bar with "Hello, world"
/// - parameter bar: A closure to call
/// - parameter theString: A string to use
func foo(bar: (_ theString: String) -> Void) {
bar("Hello, world")
}

In fact, this is the only valid approach in Swift 3 because parameter labels are no longer part of the type system (see SE-0111).



Related Topics



Leave a reply



Submit