Features like the @auto_closure keyword in Swift offer a great opportunity to extend the language in ways that might not be possible in other languages. For example, in this article we used it to implement the Ruby ||= operator.

Swift doesn’t yet have a native implementation of unless or until . These are just versions of if or while , but for when the predicate is not true. Obviously you could just stick a ! in front of your if clause, but some programmers prefer the readability of the opposing versions. The implementation of ||= could be rewritten as:

@assignment func ||=<T>(inout lhs: T?, rhs: @auto_closure () -> T) { unless(lhs) { lhs = rhs() } }

I know, you think, I can implement unless and until myself! What’s nice is you can make them look almost like the native flow control statements, because closure expressions can be outside the parentheses of function calls if they are the last argument.1 Implementing unless seems pretty straightforward:

func unless<L: LogicValue>(pred: L, block: ()->()) { if !pred { block() } } // doSomething only if condition is false unless(condition) { doSomething() }

until is a little trickier, because you need to execute the predicate multiple times as you loop. But @auto_closure can help you out.

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

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. Any variable used in both the parentheses and the block will be captured by both, so altering it in the block will affect the result of the condition (hence the condition can become true over time).

Armed with your new unless and until functions, you write a function to search a collection for the index to the first occurrence that doesn’t match a predicate:2

func findIfNot <C: Collection, L: LogicValue> (col: C, pred: (C.GeneratorType.Element) -> L) -> C.IndexType? { var idx = col.startIndex until(idx == col.endIndex) { unless(pred(col[idx])) { return idx } idx = idx.succ() } return nil } let a = [1, 2, 3] let isOdd = { $0%2 == 1} // find the first non-odd number let idx = findIfNot(a, isOdd)

Arguments about SESE and other stylistic preferences aside, this function should do the job. Except instead it generates a compiler error, 'C.IndexType' is not convertible to '()' .

Why? Because the return after the unless is a return from that closure expression between the braces, not a return from the findIfNot function. That unless closure expects no value to be returned, so when you return idx it’s an error. But if you were just blithely using this unless function you found in a library as if it were just like an if statement, you might not realize this and the compiler error may come as a shock.

Now say you instead implemented findIfNot to take in an index into the collection as an inout , and advanced that index to the first non-match, instead of returning a value (and returning the endIndex if every item matches). This is pretty bad code, but here it is:

func findIfNot <C: Collection, L: LogicValue> (col: C, inout idx: C.IndexType, pred: (C.GeneratorType.Element) -> L) { until(idx == col.endIndex) { unless(pred(col[idx])) { return } idx = idx.succ() } } let a = [1, 2, 3] let isOdd = { $0%2 == 1} var idx = a.startIndex findIfNot(a, &idx, isOdd) // idx will not be what you would hope

Now, no compiler error. Instead, this code will fail much more subtly, always returning as if no non-match was found. If we replace the unless with an if , the code will run forever because the until block will return before moving idx forward, and then loop again.

Unit tests should catch this of course. But as a user of built-in-like functions, you should always be aware they aren’t quite the part of the language they might seem. The bugs resulting might not be as obvious as in this case. Also, this is a good argument for using a more “functional“ approach, by writing general algorithms like findIfNot rarely and testing them thoroughly, and then reusing them as much as possible, rather than writing explicit loops.