How to use Swift @autoclosure

ClosuresSwift

Closures Problem Overview


I noticed when writing an assert in Swift that the first value is typed as

@autoclosure() -> Bool

with an overloaded method to return a generic T value, to test existence via the LogicValue protocol.

However sticking strictly to the question at hand. It appears to want an @autoclosure that returns a Bool.

Writing an actual closure that takes no parameters and returns a Bool does not work, it wants me to call the closure to make it compile, like so:

assert({() -> Bool in return false}(), "No user has been set", file: __FILE__, line: __LINE__)

However simply passing a Bool works:

assert(false, "No user has been set", file: __FILE__, line: __LINE__)

So what is going on? What is @autoclosure?

Edit: @auto_closure was renamed @autoclosure

Closures Solutions


Solution 1 - Closures

Consider a function that takes one argument, a simple closure that takes no argument:

func f(pred: () -> Bool) {
    if pred() {
        print("It's true")
    }
}

To call this function, we have to pass in a closure

f(pred: {2 > 1})
// "It's true"

If we omit the braces, we are passing in an expression and that's an error:

f(pred: 2 > 1)
// error: '>' produces 'Bool', not the expected contextual result type '() -> Bool'

@autoclosure creates an automatic closure around the expression. So when the caller writes an expression like 2 > 1, it's automatically wrapped into a closure to become {2 > 1} before it is passed to f. So if we apply this to the function f:

func f(pred: @autoclosure () -> Bool) {
    if pred() {
        print("It's true")
    }
}

f(pred: 2 > 1)
// It's true

So it works with just an expression without the need to wrap it in a closure.

Solution 2 - Closures

Here's a practical example — my print override (this is Swift 3):

func print(_ item: @autoclosure () -> Any, separator: String = " ", terminator: String = "\n") {
    #if DEBUG
    Swift.print(item(), separator:separator, terminator: terminator)
    #endif
}

When you say print(myExpensiveFunction()), my print override overshadows Swift's print and is called. myExpensiveFunction() is thus wrapped in a closure and not evaluated. If we're in Release mode, it will never be evaluated, because item() won't be called. Thus we have a version of print that doesn't evaluate its arguments in Release mode.

Solution 3 - Closures

Description of auto_closure from the docs:

> You can apply the auto_closure attribute to a function type that has a > parameter type of () and that returns the type of an expression (see > Type Attributes). An autoclosure function captures an implicit closure > over the specified expression, instead of the expression itself. The > following example uses the auto_closure attribute in defining a very > simple assert function:

And here's the example apple uses along with it.

func simpleAssert(condition: @auto_closure () -> Bool, message: String) {
    if !condition() {
        println(message)
    }
}
let testNumber = 5
simpleAssert(testNumber % 2 == 0, "testNumber isn't an even number.")

Basically what it means is you pass a boolean expression as that first argument instead of a closure and it automatically creates a closure out of it for you. That's why you can pass false into the method because it is a boolean expression, but can't pass a closure.

Solution 4 - Closures

This shows a useful case of @autoclosure https://airspeedvelocity.net/2014/06/28/extending-the-swift-language-is-cool-but-be-careful/

> Now, the conditional expression passed as the first parameter to until will be automatically wrapped up into a closure expression and can be called each time around the loop

func until<L: LogicValue>(pred: @auto_closure ()->L, block: ()->()) {
    while !pred() {
        block()
    }
}
 
// doSomething until condition becomes true
until(condition) {
    doSomething()
}

Solution 5 - Closures

It's just a way to get rid of the curly braces in a closure call, simple example:

	let nonAutoClosure = { (arg1: () -> Bool) -> Void in }
	let non = nonAutoClosure( { 2 > 1} )
	
	let autoClosure = { (arg1: @autoclosure () -> Bool) -> Void in }
	var auto = autoClosure( 2 > 1 ) // notice curly braces omitted

Solution 6 - Closures

@autoclosure

@autoclosure is a function parameter which accept a cooked function(or returned type) meanwhile a general closure accept a raw function

  • @autoclosure argument type parameter must be '()'
    @autoclosure()
    
  • @autoclosure accept any function with only appropriate returned type
  • Result of closure is calculated by demand

Let's take a look at example

func testClosures() {

    //closures
    XCTAssertEqual("fooWithClosure0 foo0", fooWithClosure0(p: foo0))
    XCTAssertEqual("fooWithClosure1 foo1 1", fooWithClosure1(p: foo1))
    XCTAssertEqual("fooWithClosure2 foo2 3", fooWithClosure2(p: foo2))
    
    XCTAssertEqual("fooWithClosure2 foo2 3", fooWithClosure2(p: { (i1, i2) -> String in
        return "fooWithClosure2 " + "foo2 " + String(i1 + i2)
    }))
    
    //@autoclosure
    XCTAssertEqual("fooWithAutoClosure HelloWorld", fooWithAutoClosure(a: "HelloWorld"))
    
    XCTAssertEqual("fooWithAutoClosure foo0", fooWithAutoClosure(a: foo0()))
    XCTAssertEqual("fooWithAutoClosure foo1 1", fooWithAutoClosure(a: foo1(i1: 1)))
    XCTAssertEqual("fooWithAutoClosure foo2 3", fooWithAutoClosure(a: foo2(i1: 1, i2: 2)))

}

//functions block
func foo0() -> String {
    return "foo0"
}

func foo1(i1: Int) -> String {
    return "foo1 " + String(i1)
}

func foo2(i1: Int, i2: Int) -> String {
    return "foo2 " + String(i1 + i2)
}

//closures block
func fooWithClosure0(p: () -> String) -> String {
    return "fooWithClosure0 " + p()
}

func fooWithClosure1(p: (Int) -> String) -> String {
    return "fooWithClosure1 " + p(1)
}

func fooWithClosure2(p: (Int, Int) -> String) -> String {
    return "fooWithClosure2 " + p(1, 2)
}

//@autoclosure
func fooWithAutoClosure(a: @autoclosure () -> String) -> String {
    return "fooWithAutoClosure " + a()
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJoel FischerView Question on Stackoverflow
Solution 1 - Closureseddie_cView Answer on Stackoverflow
Solution 2 - ClosuresmattView Answer on Stackoverflow
Solution 3 - ClosuresConnorView Answer on Stackoverflow
Solution 4 - Closuresonmyway133View Answer on Stackoverflow
Solution 5 - ClosuresBobbyView Answer on Stackoverflow
Solution 6 - ClosuresyoAlex5View Answer on Stackoverflow