How to use a variadic closure in swift?

2019-02-25 08:14发布

问题:

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

回答1:

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.