クロージャを関数の引数として扱うという事について纏めました。
概要 : 引数としてのクロージャ
swiftのクロージャは、関数と同じくひとまとまりの処理
として使用することができます。
また、クロージャは変数/定数
に代入することができるという性質もあるため、引数として取り扱うことも可能となります。
そこで簡単な計算式や関数を用いたサンプルコード1・2・3を見ながら、順を追って引数としてのクロージャとはどういうものなのかをコードリーディングしていきます。
サンプルコード1
単に値を渡すだけ
渡した値が5以上なら2倍して返す関数
// ②
func multiply(_ X: Int) -> Int {
// ③
print("multiply関数を実行しました")
var answerNumber: Int = X
// ④
if answerNumber >= 5 {
answerNumber *= 2
print(answerNumber)
return answerNumber
} else {
print(answerNumber)
return answerNumber
}
}
// ①
multiply(5)
// ~以下実行結果~
// multiply関数を実行しました
// 10
では処理の流れを追っていきます。
- ①により、関数
multiply
が呼び出される。
その際、引数としてInt型パラメータである5
を渡している。
引数名は省略、また型も型推論により省略されている。 - ②で関数multiplyの定義をしている。
外部引数名は省略、内部引数名としてX
、引数の型をInt型
、戻り値の型をInt型
と定義。
ここで型を定義しているので、①の呼び出し時に型推論される。 - ③print文はただ単に関数内の処理に入ったことを示したかっただけ。
③var answerNumber: Int = X
で引数として受け取ったXを変数answerNumber
に代入している。 - ④のif文で、変数answerNumberの値に手を加え出力しreturnしている。
という内容です。
今回はmultiplyの呼び出し時に5
という値をそのまま引数として渡しています。
では次はこの引数部分を関数にしてみましょう。
サンプルコード2
値の部分を関数にする
- 値に+1をするという関数
- 5以上なら2倍して返す関数
この2つの関数を組み合わせたものになります。
// ③
func multiply(_ X: Int) -> Int {
print("multiply関数を実行しました")
var answerNumber: Int = X
if answerNumber >= 5 {
answerNumber *= 2
print(answerNumber)
return answerNumber
} else {
print(answerNumber)
return answerNumber
}
}
// ②
func add(_ number: Int) -> Int {
print("add関数を実行しました")
return number + 1
}
// ①
multiply(add(4))
// ~以下実行結果~
// add関数を実行しました
// multiply関数を実行しました
// 10
処理の流れを追っていきます。
- ①により、関数
multiply
が呼び出される。
その際、引数としてadd(4)
という関数が設定されている。
実行結果からも分かるように、multiplyに渡す前にaddが呼び出され処理が実行される。 - ②Int型を受け取り、Int型を返す処理。今回は
4
を受け取り+1し5
を返している。 - ③これ以降はサンプル1と全く同じ。
add(4)
の戻り値がInt型5
であり、引数としてそれを受け取っている。
サンプルコード3
値の部分をクロージャにする
処理の内容は変わらないが、実行順が変わることがポイントとなる。
// ②
func multiply(_ X: () -> Int) -> Int {
print("multiply関数を実行しました")
// ③
var answerNumber: Int = X()
// ⑤
if answerNumber >= 5 {
answerNumber *= 2
print(answerNumber)
return answerNumber
} else {
print(answerNumber)
return answerNumber
}
}
// ④
func add(_ number: Int) -> Int {
print("add関数を実行しました")
return number + 1
}
// ①
multiply { add(4) }
// (実行結果)
// multiply関数を実行しました
// add関数を実行しました
// 10
同じように処理の流れを見ていきます。
-
①により、関数
multiply
が呼び出される。
その際、引数としてクロージャ{ add(4) }
が使われている。
クロージャを引数に指定することによって、クロージャそのものを() -> Int型
のパラメータとして関数に渡している
※実行順を見てみると、クロージャの中身であるadd関数はすぐに実行されていないことが分かります。これは引数をクロージャで包むことによって、必要があるまで処理を遅延させることができるのです。 -
②で関数multiplyの定義をしている。
外部引数名は省略、内部引数名としてX
、引数の型を() -> Int型
、戻り値の型をInt型
と定義。
ここで型を定義しているので、①の呼び出し時に型推論される。 -
③
var answerNumber: Int = X()
で引数として受け取ったXを変数answerNumber
に代入している。
ここでXに代入されているクロージャ{ add(4) }
のパラメータの数値が必要になる。 -
④
{ add(4) }
によりadd関数が呼び出される。Int型を受け取り、Int型を返す処理。4
を受け取り+1し5
を返している。 -
add関数の実行により、Xが持つパラメータの
5
が変数answerNumber
に代入されている状態。以降if文により分岐処理が行われる。
以上が処理の流れとなっています。
補足:実行順が逆になっていることについて
サンプル2と比べると、multiply関数とadd関数の実行順が逆になっていることが違いとなっています。
これは遅延評価と言い、処理をクロージャで包むことによって評価を遅らせることができるクロージャの仕様となります。渡した先で、実際にその引数の処理が求められた時に、クロージャが実行されます。
{}の中身の処理は一旦保留し、中身の処理ごと纏めて一つの引数として扱うという感じです。
あくまで私のイメージなのですが、数学で
(5a+2)^{2} + 7(5a+2)
a = "果てしなくめんどくさい計算式"
というような式があった時に、(5a + 2)をX
などに置き換えて
X^{2} + 7X
とすることがあると思います。
(5a + 2)の中身の計算は一旦保留して他の箇所の計算をするような時ですね。
これと同じで、{処理}
をX
に置き換えて引数として渡しているのが、今回のクロージャ式となります。
multiply { add(4) } の原型を考えてみよう
クロージャの基本形は以下の形
{ (引数名1: 型, 引数名2: 型, ...) -> 戻り値の型 in return 処理 }
クロージャの省略について
・型推論が可能な場合は、型の表記を省略できる。
・クロージャ内の文がひとつしかない場合はreturnを省略可能
ということを抑えたうえで、考えてみます。
引数名: なし
引数の型: ()
戻り値の型: Int
処理: add(4)
となり、原型はmultiply( {() -> Int in return add(4)} )
であると考えられます。※エラーなく動きます。
ここから省略されていきますが、今回のケースだと
- 関数定義で型が明確に記載されている。
型が省略され、multiply( { return add(4) } )
となる。 - クロージャの処理文が一つのみ。
returnが省略される。multiply( { add(4) } )
- トレイリングクロージャを使用し、クロージャをカッコの外に出せる。
multiply() { add(4) }
※引数の一番最後がクロージャの場合のみ - 引数はクロージャのみなので()も省略可能。
multiply{ add(4) }
という過程でサンプルコードの形になります。
後語り
ここまでで関数の引数に、クロージャが使用された場合の扱い方や読み方について纏めさせていただきました。
クロージャは省略が可能だったり変数や定数に代入可能ということもあり、初学者にとってつまづきやすい箇所にも思えます。
どんな処理が行われているのかパッとわからない時は、省略形→原型と戻し、順を追って確認していくということをしてみると、理解しやすくなるのでおすすめです。