
クロージャーが理解できなんです…ゆっくりと説明してほしいな…
Go言語のクロージャーについてゆっくりと解説していきます。

私自身、クロージャーを理解するまでは本当に苦労しましたね。
この記事では、できるだけ難しい言葉を使わずにプログラムで動きを確認しながら解説していきます。
Go言語のクロージャーを解説する前に
まずは、クロージャーの意味を引用します。
クロージャ(クロージャー、英語: closure)、関数閉包はプログラミング言語における関数オブジェクトの一種。いくつかの言語ではラムダ式や無名関数にて利用可能な機能・概念である。引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。関数とそれを評価する環境のペアであるともいえる。
引用元:ウキペディア

私はこの日本語を読んで理解することができませんでした…
それで、私はクロージャーの概念を知ることをあきらめて…
という発想に切りかえて、プログラムで動きを確認し理解してきました。
私がクロージャーを理解できた順番通りに、プログラムをご紹介いたします。
話が周りくどくなるかもしれませんが、お付き合いください。
Go言語のクロージャーをゆっくり解説
それでは、Go言語のクロージャー解説を開始します。
事前知識として、関数の基礎と無名関数を理解しておく必要があります。
当ブログ記事を参考にしてみてください。


クロージャーを使う前のプログラム〜その1〜
まずは、下記のプログラムを確認してください。
package main
import "fmt"
func main() {
x := 0
// 変数に無名関数を代入
add := func() int {
x++
return x
}
fmt.Println(add())
fmt.Println(add())
fmt.Println(add())
}
// 出力結果
$ go run main.go
1
2
3
このプログラムは関数が実行されるたびに、変数x
が 1
増加するプログラムです。
このプログラムのポイントは、関数が実行されても 変数x
の値は初期化されずに変わらない。

ということです。
でも、このプログラムに問題が潜んでいて、変数x
の値は簡単に変えれるのです。
それではまずいので、main関数の外に出して関数化してみましょう。
クロージャーを使う前のプログラム〜その2〜
では、先程のプログラムをmain関数の外で関数化してみます。
package main
import "fmt"
func addGen() int {
x := 0
x++
return x
}
func main() {
// 関数を実行する
fmt.Println(addGen())
fmt.Println(addGen())
fmt.Println(addGen())
}
// 出力結果
$ go run main.go
1
1
1
プログラムの出力結果を見てわかる通り、見事に変数が初期化されています。
これだと、関数が呼ばれるたびに変数が初期化されてしまします。
ここで登場するのがクロージャーです。
クロージャーを実装する
ここでクロージャーを実装します。
ポイントとしては、関数の戻り値に関数を指定することと戻り値の関数は無名関数であるということです。

わかりずらいですよね…実際にプログラムを確認してみましょう。
package main
import "fmt"
// クロージャーの実装
func addGen2() (func() int) {
x := 0
return func() int {
x++
return x
}
}
func main() {
// addGen2は関数が戻り値なので変数に代入する
add2 := addGen2()
fmt.Println(add2())
fmt.Println(add2())
fmt.Println(add2())
}
// 出力結果
$ go run main.go
1
2
3
出力結果が最初と同じになりました!
さらにポイントとなるのが、 add2 := addGen2()
の部分です。
変数add2
の中の関数はどうなっているのでしょうか?
// 元の関数
func addGen2() (func() int) {
x := 0
return func() int {
x++
return x
}
}
// 変数add2の中の関数の正体!
// ココ重要!!
func() int {
x++
return x
}
変数add2
の関数は上記のコードを確認してみると、fmt.Println(add2())
が実行されても x := 0
の処理が無いので 変数x
は初期化されることないのです。
なので、add2 := addGen2()
が参照され続けている限り初期化処理がされないのです。
では、もう一度初期化して計算し直す場合は変数を新たに宣言すれば大丈夫です。
それも、プログラムで確認していきましょう。
package main
import "fmt"
// クロージャーの実装
func addGen2() (func() int) {
x := 0
return func() int {
x++
return x
}
}
func main() {
// addGen2は関数が戻り値なので変数に代入する
add2 := addGen2()
fmt.Println(add2())
fmt.Println(add2())
fmt.Println(add2())
// 新たに変数add3を宣言してaddGen2の戻り値を代入
add3 := addGen2()
fmt.Println(add3())
fmt.Println(add3())
fmt.Println(add3())
}
// 出力結果
$ go run main.go
1
2
3
1
2
3

このプログラムこそ、クロージャーの一番簡易的なプログラムの動きなのです。
【まとめ】Go言語のクロージャーについて
私がいろんなブログ記事や動画教材を見て、やっと辿り着いたクロージャーについて理解です。
クロージャーの概念を知ろうとすると、プログラミング初心者は間違いなく挫折します。
それは、普段聞き慣れない単語が多く単語の意味を調べても意味がわからないという、理解できない無限ループにハマるからです。
だから、クロージャーの概念ではなくプログラムの動きからクロージャーを理解していきましょう。
以上です。
クロージャーはプログラミングでも難しい部類のひとつだと思います。
わからなくても大丈夫です。
わからなければ次に進んでGo言語に多くふれていき、またクロージャーを学習すれば理解できることでしょう。
諦めずに頑張っていきましょうね!
comment