auto_closure属性
引数を取らず、何らかの値を返す関数(() -> T)を引数とするような関数やクロージャ式に対しては、引数宣言にauto_closure属性を用いることができます。auto_closure属性を用いた関数引数に対しては、次のような処理が行われます。
- 単なる式に対してクロージャ式に使う括弧を自動的に被せるように変換される
- 属性のついたパラメータに関しては評価を遅延させることができる
例として、何も値を取らず、Bool値を返す関数を引数に持つ次のような関数を考えてみましょう。
func lazyAnd(lhs: Bool, rhs: () -> Bool) -> Bool { if lhs { return rhs() } return lhs } lazyAnd(1 > 3, {1 < 3}) // false
このlazyAnd関数は「&&」演算子を関数にしたものです。この関数宣言の関数引数に対して、auto_closure属性を適用してみましょう(属性の頭には「@」がつきます)。
func lazyAnd(lhs: Bool, rhs: @auto_closure () -> Bool) -> Bool { if lhs { return rhs() } return lhs } lazyAnd(1 > 3, 1 < 3) // false
lazyAnd関数を呼び出すときの末尾の引数から、{}カッコがはずれました。auto_closure属性を適用した引数に与えられた式(ここでは1 < 3)は、引数を持たないクロージャへ自動的に包み込まれるため、{}カッコをつけないのです。
ところで、lazyAnd関数を呼び出している文を見ると、第2引数としてBool値(1 < 3を評価した結果のtrue)を受け取っているように見えます。しかし、lazyAnd関数が第2引数で受け取っているのは、式1 < 3を包み込んだクロージャです。そのため、式1 < 3は第1引数がtrueだったときに、lazyAnd関数内部のif文の中ではじめて評価されます(rhs()により)。
もし、第1引数がfalseだったら、式1 < 3は一度も評価されません。つまり、auto_closure属性によって式をクロージャとしてキャプチャすることで、その評価を遅延させることができ、結果的に無駄な計算をカットすることが可能になります。
クラス・ストラクチャのプロパティ宣言で用いることが可能なlazyキーワードについても、このような遅延の考え方を適用できますが、その解説は次回以降に行います。
ネスト関数クロージャによる値のキャプチャ
ネストされた関数は値をキャプチャするため、注意が必要です。例として、次のような関数を考えます。
func makeStrAppender(str: String) -> () -> String { var acc = "" func appender() -> String { acc += str return acc } return appender }
makeStrAppender関数の戻り値はネスト関数appenderで、呼び出し元ではそれを値(関数型の値)として変数に代入できます。また、appender関数は、makeStrAppender関数内のacc変数をキャプチャしています。そのため、appender関数を代入した変数を使ってappender関数が呼び出されると、acc変数はその影響を受けます。
var appendPlus = makeStrAppender("+") appendPlus() // "+" appendPlus() // "++" appendPlus() // "+++"
関数を代入した変数に再度関数を代入すると、代入してあった関数インスタンスは破棄され、キャプチャされた値が初期状態に戻ります。
appendPlus = makeStrAppender("+") appendPlus() // "+"
関数、クロージャ式を含めたクロージャは参照型のオブジェクトとして扱われるため、代入時には関数インスタンスのコピーではなく、関数インスタンスへの参照が渡されます。その関数インスタンスがキャプチャした変数や定数も引き継がれます。
次のコードを見てください。appendPlusRef変数には、appendPlus変数に代入されているappender関数への参照が渡されるため、appendPlusRef()によりacc変数は"++"になり、最後のappendPlus()によりacc変数は"+++"になっています。
var appendPlus = makeStrAppender("+") appendPlus() // "+" let appendPlusRef = appendPlus appendPlusRef() // "++" appendPlus() // "+++"
次回は
今回は、Swiftの関数とクロージャを解説しましたが、いかがでしたでしょうか。関数型言語とスクリプト言語の“いいとこ取り”をしたような構文で、特にプログラミング言語好きな方は「おもしろい!」と感じたのではないでしょうか。やや難解ではありますが、その強力さはプログラミングの効率化に大きく寄与すると思います。
次回は、やはりSwiftの特徴的な機能であるクラスとストラクチャ、列挙型のオーバービューをした後に列挙型とオプショナル列挙型を取り上げます。お楽しみに。