2014年7月22日火曜日

[Swift] @autoclosure 属性 の使いどころ

Xcode6-Beta5 にて @auto_closure は @autoclosure に変更された。
同様に LogicValue は BooleanType になった。
 [新] @autoclosure ← [旧] @auto_closure
 [新] BooleanType ← [旧] LogicValue
この変更にあわせて記事を修正した。

Apple Swift Blog にて @autoclosure 属性による遅延評価が紹介されていた。



C の assert() 実装を Swift で行った場合の例が引き合いに出されていて分かりやすい。

C 言語の assert 実装はマクロであるから、リリースビルドでは ((void)0) と展開されて、式の評価自体がない。
同様の実装を Swift で考えたとき、以下のような関数 func assert(x: Bool) の宣言では常に式の評価が発生してしまう。
func assert(x: Bool) {
#if !NDEBUG
    /*noop*/
#endif
}

assert(someExpensiveComputation() != 42)
というわけで、引数をクロージャに変更。するとシンタックスが残念な感じになる。
assert({ someExpensiveComputation() != 42 })
そこで @autoclosure の登場。
@autoclosure 属性は関数の引数に指定でき、渡された式をクロージャとしてラップする。
func myassert(predicate: @autoclosure () -> Bool) {
#if !NDEBUG
     if predicate() {
          abort()
     }
#endif
}
呼び出しが自然で遅延評価可能になる。
myassert(someExpensiveComputation() != 42)

標準ライブラリでは && 演算子も @autoclosure 引数を取るように宣言されていて、右オペランド(の式)は自動的にクロージャとしてラップされ、左オペランドが false であった場合は右オペランドの評価はされない。
func &&(lhs: BooleanType, rhs: @autoclosure () -> BooleanType) -> Bool {
   return lhs.boolValue() ? rhs().boolValue() : false
}
もし rhs が単なる BooleanType であったなら、&& が実行された段階で rhs が評価されてしまうし、 通常のクロージャ引数 rhs: () -> BooleanType にすると rhs の評価を遅延させることが出来るけども、以下のように面倒な書き方になってしまう。
let result = A() && { B() }

てなワケで、評価は遅延させたいけどもクロージャではなくて単純な式を渡せた方が良い場合に @autoclosure 属性は便利ですね、という話。

ちなみにこの属性が適用されたクロージャは引数を取ることは出来なかった(コンパイルエラーとなる)。式が自動的にラップされるので引数指定する機会がないし、当然か。

0 件のコメント:

コメントを投稿