swiftvariablessyntaxclosures

Using @autoclosure in Swift: Can It Behave Like Overloaded Functions?


I have a function that uses @autoclosure. Is there a way to make @autoclosure behave similarly to overloaded functions, so it automatically knows which syntax is right for variables like in my case?

func myEnvironment(_ bindings: @autoclosure () -> [Int], _ body: ([Int]) -> Int) -> Int {
    let vars = bindings()
    return body(vars)
}

// prints 4
let result = myEnvironment([]) { _ in
    2 + 2
}

// Error
// prints 6
let result2 = myEnvironment {
    let x = 1
    let y = 2
    let z = 3
    return [x, y, z]
} _: { vars in
    vars[0] + vars[1] + vars[2]
}

Error

// prints 6
let result2 = myEnvironment {
    let x = 1
    let y = 2
    let z = 3
    return [x, y, z]
} _: { vars in
    vars[0] + vars[1] + vars[2]
}

error 1:
Contextual type for closure argument list expects 1 argument, which cannot be implicitly ignored

error 2:
Cannot convert value of type '[Int]' to closure result type 'Int'

Is there a way to achieve the desired behavior, or do I need to modify my code?

I saw this post How to use Swift @autoclosure but it did not answer my question. Any suggestions are appreciated.


Solution

  • I assume you want to also allow the caller to pass in a code block that contains multiple lines of code as the autoclosure.

    You can declare another overload that does not take an @autoclosure.

    // The '@autolcosure' overload can delegate to the new overload:
    func myEnvironment(_ bindings: @autoclosure () -> [Int], _ body: ([Int]) -> Int) -> Int {
        myEnvironment(bindings, body)
    }
    
    func myEnvironment(_ bindings: () -> [Int], _ body: ([Int]) -> Int) -> Int {
        let vars = bindings()
        return body(vars)
    }
    
    // ------------
    
    // or the other way around!
    func myEnvironment(_ bindings: () -> [Int], _ body: ([Int]) -> Int) -> Int {
        myEnvironment(bindings(), body)
    }
    
    func myEnvironment(_ bindings: @autoclosure () -> [Int], _ body: ([Int]) -> Int) -> Int {
        let vars = bindings()
        return body(vars)
    }
    

    If you cannot add overloads for some reason, you can pass an "immediately invoked" closure instead. Because of @autoclosure, this closure isn't actually immediately invoked.

    myEnvironment({
        let x = 1
        let y = 2
        let z = 3
        return [x, y, z]
    }()) { x in
        vars[0] + vars[1] + vars[2]
    }