Swift言語ガイド 第7章 クロージャ
- クロージャとは自己完結型の機能ブロックである。C で言えばブロックに似ている。定義されたコンテキストにより、どんな定数にも変数にも格納可能で、「閉じている」ため、クロージャと呼ばれる。
- 関数は名前がついたクロージャということができる。逆に、クロージャは名前のない関数と言うと語弊があるが、最初のうちはこの理解でもよかろう。
- クロージャの一般形は以下。
{ ( 引数 ) -> 戻り値型 in 文 }
- 標準ライブラリにある
sorted
に渡すクロージャの例がとてもおもしろいので、これを追ってみる。sorted
は、既知の型の配列をソートするもので、そのソートはユーザが提供するクロージャに基づいて行われるというものである。引数は2つ。ソートさせる配列と、引数を2つ取り、Bool
を返すクロージャである。Bool
値は、2つ取る引数の最初の方が後の方より前にソートされるはずならばtrue
を、そうでなければfalse
となるようにクロージャを作る必要がある。まずは最初のわかりやすい例から。これは文字列をアルファベットの逆順にソートするものである。関数(クロージャ)の型は(String, String) -> Bool
である。- まずは普通に関数を定義し、それを渡す。
let names = [ "くるみ", "アル", "ひでかず", "はぎ", "くに" ] func backwards( s1: String, s2: String ) -> Bool { return s1 > s2 } var reversed = sorted( names, backwards )
上記の関数でs1 > s2
を返しているが、アルファベット順じゃなくて逆順ソートなので、true
を返すのはクロージャの1番目の引数が2番目の引数より大きいときであるので、これで良い。この式がクロージャの本質である。 - これをインラインのクロージャにすると、次のようになる。これは完全にクロージャの一般形と同じ形をしている。
var reversed = sorted( names, { ( s1: String, S2: String ) -> Bool in return s1 > s2 } )
- それで、sorted に渡すクロージャの戻り値は
Bool
であると決められているので-> Bool
は省略できる。また引数の型も渡す配列から推論できるので省略できる。そうすると、1行に入ってしまって次のように書くことができる。var reversed = sorted( names, { s1, s2 in return s1 > s2 })
- 式一つしかないクロージャは暗黙にその式の結果を戻り値にするので、
return
も省くことができる。var reversed = sorted( names, { s1, s2 in s1 > s2 })
- Swift は
$0
,$1
,$2
, ... という省略形の引数名をクロージャに使えるので、これを使えば引数定義の部分を省略することができる。引数部分も戻り値部分もなければ、in
も省略できる。そうするとこうなる。var reversed = sorted( names, { $0 > $1 })
- Swift の文字列型は、独自に大なり比較演算子を関数として定義しているので、それを使えばさらに短くでき、なんと1文字で済んでしまう。
var reversed = sorted( names, > )
- クロージャをここまで短くすることができるという例である。どこまで省略して書くかは個人の好みの問題だと思うが。
- まずは普通に関数を定義し、それを渡す。
- 末尾クロージャ Trailing Closures は、クロージャー式を関数の最後の引数として渡す場合に使われるもので、この場合、クロージャ式は渡す先の関数の引数のかっこの外側に書くことができる。なお、クロージャ式が関数のただ一つの引数の場合で、それを末尾クロージャとして書く場合は、関数呼び出し時に
()
を付ける必要はない。func callee( closure: () -> () ) { // callee の実行部 } // callee を普通に呼ぶ呼び方 callee( {} ) // 終了クロージャだとこうなる。 callee(){}
- 末尾クロージャを使えば、reversed 関数はこう書ける。
var reversed = sorted( names ) { $0 > $1 }
- 末尾クロージャは特に、クロージャが長くてインラインでは収まらない時などに便利である。Swift の持つ
Array
型のmap
メソッドはただ一つの引数としてクロージャ式を取るが、その例は以下。let digitNames = [ 0: "ゼロ", 1: "いち", 2: "に", 3: "さん", 4: "よん", 5: "ご", 6: "ろく", 7: "なな", 8: "はち", 9: "きゅう" ] let numbers = [ 16, 58, 510 ] let strings = numbers.map { ( var number ) -> String in var output = "" while number > 0 { output = digitNames[ number %10 ]! + output /* ディクショナリの添字記法はオプショナルを返すがこの場合は必ずあるので ! をつけている。 */ number /= 10 } return output }
- クロージャは、それを定義した時の周りのコンテキストから定数や変数を「キャプチャ(取り込み capture)」できる。そしてそれらの値を、たとえ周りのコンテキストがないところでも参照したり変更したりできる。ネストした関数が外側の関数の定数や変数を使えるのはこのためである。
- クロージャは参照型である(値型ではない)。なので関数もまた参照型である。従って、例えば2つの定数にクロージャを別々に代入したとしても、実体は両方とも同じクロージャを指す。
- 関数はおなじみだがクロージャは割と新しい気がする。使いこなせば、
sorted
のところで見たように、強力な武器となろう。
(第7章 クロージャ 終わり)