How to use Swift @autoclosure
ClosuresSwiftClosures 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()
}