Swiftについて勉強していて、クロージャがよくわからなくなった。
サンプルコード見ると、なんとなくやってることはわかるが、
いくつコードを見ても、「じゃあ本質的にクロージャって何なの?」というのがつかめなかった。
ググってみたら、なんかみんな混乱していて、
僕が頭悪いんじゃなくて、普通に難しい概念なんだなというのがわかって一安心した。
ネット記事の「わかりやすいクロージャ」系の記事は、記述がバラバラ過ぎて余計に混乱したので、
自分なりの理解を書きたいと思う。
最初の理解(誤解)
JavaScriptは少し心得があった。
JSの解説では、クロージャは、
処理が終わっても、変数を保持したいときに使う関数。
ガーベジコレクションの対象外なので、処理が終わっても残る
という説明がされていて、クロージャの存在意義は静的に変数の中身を保持できることだと思っていた。
Swiftの解説本(「SWIFT実践入門」)を読むと、以下のように書いてある。
クロージャは、再利用可能なひとまとまりの処理です。
(中略)
クロージャ式は、名前が不要であったり、
型推論によって型の記述が省略可能であったりと、関数より手軽に定義できます。
「え?! Swiftにおけるクロージャって無名関数のこと言ってんの?」
と最初思いました。
JSとSwiftでクロージャの仕様が微妙に違うのは事実でしたが、これは勘違いでした。
無名関数とクロージャは全然別の概念でした。
クロージャってなに?
wikipediaによると、クロージャはこんな定義です。
クロージャ(クロージャー、英語: closure)、関数閉包はプログラミング言語における関数オブジェクトの一種。いくつかの言語ではラムダ式や無名関数で実現している。
あーなるほどね。
完璧にわかったわ。
読んでる皆も完璧にわかったんじゃない?
関数閉包ね。なるほど〜。
クロージャってなに?(二回目)
せっかく調べたのでもうちょっと書きます。
クロージャ自体は、情報系における一般的な概念。
思想自体は1960年からあるよう。
乱暴に言ってしまうと、定義した関数の中の変数をそのスコープ外から使っても更新し続けられる、という風な説明になります。
わかりやすいかは微妙ですが、イメージ図を描いてみました。
長方形はメモリ空間のイメージです。
左がクロージャではないプログラムの処理、右がクロージャの処理です。
丸1、丸2が関数を呼び出す処理です。
クロージャを使わない場合、丸1、丸2が更新をかけるのは、元の関数を別のアドレスにコピーした値となります。
故に、丸1、丸2の更新結果は必ず独立しています。
@discardableResult //戻り値なくても警告出さないように
func funcIncrement() -> Int{
var count = 0
count += 1
return count
}
funcIncrement() //1
funcIncrement() //1
これを、クロージャ使ってやると、スコープ外の呼び出しに対しても変数の値が初期化されず、継続されます。
let Increment: () -> Int //(Void) -> Int型のクロージャを定義
var count = 0
Increment = { //ここから実際のクロージャ定義
count += 1
return count
}
Increment() //1
Increment() //2
最初見たとき、countを初期化するんじゃないか?と思ったのですが、
変数のキャプチャをするため、二度目以降は変数が定義されず、
丸1の処理で定義した変数とその値を引き継ぐようです。
どういうときに使ったらいいの?
初心者なので、イマイチ使いみちが思い浮かばないというのが正直なところです。
ただ他の人が書いてるコード見ると、引数なり戻り値なりに使うパターンが多そうです。
Swiftにおけるクロージャ
- グローバル関数は、名前があり、値をキャプチャしないクロージャです。
- ネストされた関数は、名前があり、囲っている関数にある値をキャプチャできるクロージャです。
- クロージャ式は、周りのコンテキストにある値をキャプチャできる、軽量シンタックスで記述された名前が無いクロージャです。
The Swift Programming Language 日本語訳
クロージャの何がうれしいの?
スコープ外から、別スコープの変数を保持できる……というのは、
たしかにうれしいような気がしますが、そもそもスコープが分かれてるのは、
スコープ外から変数を破壊されないようにしたいのでそういう仕組みになっているのだから、
別スコープから使うなや、と僕は最初思いました。
ただ結局、最近のプログラミング言語では、引数に関数を入れるとか、戻り値に関数を入れるとかが当たり前になっているので、
そういうときにクロージャを使うときれいに記述できるので、みんなうれしいみたいです。
オブジェクト指向の発想なら、クラス作って、そこに値保持して……としなければいけないようなケースでも、
気にせずクロージャを突っ込んで終わり、みたいな。
個人的には下記の説明が一番納得感高かったです。
私が思う結論から言いますと、関数型プログラミングの発想でプログラミングをしない限り、クロージャは役に立つものではありません。旧来のJava的な発想のコードなら、たぶん、使うことは無いでしょう。ですが、関数型プログラミングを取り入れようとした瞬間、クロージャ無しで様々な動作を実現することはかなり厳しいと言えると思います。逆に言えば、関数型プログラミングを知らない限り、クロージャのありがたみはあまりわからないという事です。
(クロージャについて詳しく教えてください(teratail))
まだ自分でクロージャを便利に扱える気はしないのですが、
Swiftのコード読むときには焦らないようにしたいなと思います。
※内容に誤りがあれば、ご指摘おねがいします!