クロージャはつまづく
何故、つまずくのか、私なりの気づきをメモにする。
理由はいくつかあるが、私の場合で行くと、まず関数から勉強しているからである。
そうしてある日突然、トレイリングクロージャから出てくるからである。(偏見かも)
コードの簡略化や記述のルールがわからないからである。
最小限のクロージャについて学んだ際に、「それを無名にする必要があるのか?」がわからないからである。
何故クロージャを理解する必要があるのか
理解すると、コードを書くのが面白くなり、さらにデータの受け渡しを簡素にできたりして非常に便利だからである。
クロージャ自体は、一見、読みづらいのだが、トレイリングクロージャとして、汎用的に扱われており、コードを読むためにも必要だからだ。
関数とクロージャを見比べる
printMessageMethodメソッドと変数printMessagesを比較
//関数
func printMessageMethod(count:Int)->Void{
for i in 0..<count {
print("おはよう")
}
}
//クロージャ
let printMessages:(Int)->Void = { count in
for i in 0..<count {
print("Hello")
}
}
printMessageMethod(count: 5)
printMessages(5)
いずれも引数に入れた数だけprintが実行される。
クロージャの概念の寄り道
クロージャは、日本語訳すると、閉鎖、締め切り、閉店、休業、終止、終結、討論終結などに該当するらしい。
コードを書くうえで、英語であることとその単語の語義がわかりにくいことが非常に大きな障壁になると、私は思う。
funcはfunctionの略だと、受け取りやすかった。
個人の感想だが。ファンクションと言われたときに、「何らかの機能だな」と想像しやすかったので、とっつきやすかった。
クロージャと聞いたときに、「締め切り」というイメージとは結びつかなかった。Javaにおけるラムダ式であり、JavaScriptにおける無名関数であり、さらに、Swiftのすべての関数はクロージャである、みたいな説明を度々目にするのだが、これでは、私にはわからなかった。
まえがきが多くなった・・・。
小さいクロージャを作る
let printMessages = {
print("Hello")
print("Message")
}
私の考える小さなクロージャはこれである。
ややこしいのだが、この小さすぎるクロージャでも更に細分化して説明がいる。
let printMessages の部分は変数宣言。
=は、代入演算子だと思う。(名前が違うかもしれません・・・)
{}で囲まれた部分がクロージャである。
これは、変数と関数を見慣れた私にとって、変数でもなく、関数でもなく、しかしどちらにも近しいと感じる謎のコードに該当する。
関数名もなく、変数なのに、囲い込みの記号{}があって、その中で処理がある。
クロージャの引数型を増やす
let printMessages2:(Int,String) -> Void = { num,message in
for _ in 0..<num {
print(message)
}
}
printMessages2(2,"こんにちは")
クロージャを理解するうえで、型理解も大事な要素である。
この場合、(Int,String) -> Voidがこのクロージャの型である。
関数のときは、関数名(引数名1:引数型, 引数名2:引数型)->返り値型だった。
クロージャのときは、関数名はなく、(引数型,引数型)->返り値型である。
これでは、引数をどうやって使うのかわからないのだが、(Int,String)と引数方を指定すれば、{ num,message in <#code#> }のように、{}囲い込みの中で引数名を指定して、扱うことができる。
引数型を変えてみる
let messages = ["こんにちは","ニーハオ","おはよう"]
let greetingMessages:([String]) -> Void = { greetings in
for greeting in greetings {
print(greeting)
}
}
greetingMessages(messages)
引数は、配列でも良い。
どうして、配列の変数名はmessagesにして、クロージャ側の引数名をgreetingsにしたかというと、{}の中で用意した名前とスコープ外の名前が一致していなくてもいいことを確認するためだ。
引数名すらも要らないなら、{ _ in }にしてもいい。
けれど、名前はあったほうがいい。後でコードを見直した人が、他人であっても自分であっても、名前はほしいはず。(補足 : 修正の際に、名前がないと、扱っている値が何者であるのか、確認する手間が発生する)
クロージャで他の変数から値を受け取り、返り値を変数に格納する
let colorByColorNum:(Int) -> Color = { num in
switch num {
case 0:
return Color.blue
case 1:
return Color.red
case 2:
return Color.brown
default:
return Color.orange
}
}
var colorNum = 0
var color7 = colorByColorNum(colorNum)
変数に応じて、返り値が違うというパターンを用意してみた。
0を代入すると、青色が返ってくる。
1を代入すると、赤色が返ってくる。
2を代入すると、茶色が返ってくる。
それ以外だと、オレンジ。
IntとStringばかりだと味気がないのでなんとなくColor。
(Color型はimport SwifUIが必要)
トレイリングクロージャにしてみる
クロージャだけで使うのではなく、関数の引数の中にひとつだけクロージャを置くのが、使いやすいと私は思う。
クロージャも関数も変数もみんな型なので、入れ子にして使うことができる。できるけれど、入れ子にして使いやすいパターンや慣習化されているパターンというのがある。おそらく、トレイリングクロージャもそうだと思う。
var color6 = Color.green
/// カウント値を受け取り、トレイリングクロージャを呼び出す関数
/// - Parameters:
/// - count: 処理対象の数値
/// - completion: `count`に基づいて何らかの処理を行うクロージャ
func applyTrailingLogic(count:Int, completion: @escaping (Int)->Void) {
print("count \(count)")
completion(count)
}
applyTrailingLogic(count: 0){ num in
if num == 0 {
color6 = .blue
}else {
color6 = .orange
}
print("color6 \(color6)")
}
上記では、applyTrailingLogicメソッドを定義して、applyTrailingLogicを呼び出す際に、トレイリングクロージャを呼び出す。
まとめ
クロージャからトレイリングクロージャまで噛み砕いてコードのパターンを載せてみた。
クロージャだけで便利に使えるケースも有るといいのだが、トレイリングクロージャを見かけることが多い。
最小単位のクロージャや型引数などを工夫していろんなパターンに慣れていきたい。