前回までのあらすじ
第1回目は「型ごとの特徴」について学びました。もしよかったらご覧ください。
今回も引き続きUdemyの『【Go入門】Golang基礎入門 + 各種ライブラリ + 簡単なTodoWebアプリケーション開発(Go言語)』の内容をもとに学習を進めていきます。
クロージャーとは
wikiでクロージャーを調べると以下のような説明がありました。
関数閉包はプログラミング言語における関数オブジェクトの一種。いくつかの言語ではラムダ式や無名関数にて利用>可能な機能・概念である。引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において>解決することを特徴とする。関数とそれを評価する環境のペアであるともいえる。
ちょっと何を言っているのかわからないですが、簡単にいうと関数の外で定義された変数の状態を保持し、後で使用することができる関数のことだと思っています。実際にコードベースで見た方がわかるかなと思うので早速実践していきます。
Go言語でクロージャーを書いてみた。
func AddCounter() func() int {
var store int = 1
return func() int {
s := store
store++
return s
}
}
func main() {
f := AddCounter()
fmt.Println(f()) // 1
fmt.Println(f()) // 2
fmt.Println(f()) // 3
fmt.Println(f()) // 4
}
これがクロージャーというものです。
main()
でf := AddCounter
とf
に代入してから、4回呼び出すとそれぞれ違う値が返ってきます。
これはAddCounter()
が変数の状態を維持しており、関数が呼ばれるたびに前回の状態を保持した状態から処理を始めるからです。仕組みを1つずつみていきましょう。
f := AddCounter()
との記載があるため、f
には以下の無名関数が入ってきます。
func() int {
s := store
store++
return s
}
1回目に呼び出した時の処理からみていきましょう。呼び出された時にAddCounter()
のstore
は1
です。結果的に最初の処理では1
を出力します。
//AddCounter()で定義されている変数
var store int = 1
func() int {
s := store // sにstore(1)を入れる。
store++ // store = 2 になる。
return s // 現在sには1が入っている。
}
2回目です。呼び出された時にAddCounter()
のstore
は2
になります。なぜかというと、先ほどのstore++
の処理でstore = 2
になっているからです。
//AddCounter()で定義されている変数
var store int = 2
func() int {
s := store // sにstore(2)を入れる。
store++ // store = 3 になる。
return s // 現在sには2が入っている。
}
あとはこれの繰り返しです。
//3回目
//AddCounter()で定義されている変数
var store int = 3
func() int {
s := store // sにstore(3)を入れる。
store++ // store = 4 になる。
return s // 現在sには3が入っている。
}
//4回目
//AddCounter()で定義されている変数
var store int = 4
func() int {
s := store // sにstore(4)を入れる。
store++ // store = 5 になる。
return s // 現在sには4が入っている。
}
結果的に関数を呼ぶたびに数値が増えるということになります。
使い道
状態を持つ関数
関数が内部状態を持つようにすることができます。例えば、カウンターを保持する関数を作成できます。
ファクトリ関数
特定の設定に基づいて異なる動作をする関数を作成できます。動的に異なる振る舞いを持つ関数を生成することができます。
データのカプセル化
外部変数の状態を閉じ込め、外部から直接アクセスできないようにしながら、制御された方法でアクセス・変更できる関数を作るのに役立ちます。
という感じの使い方があるそうです。ただ、私はまだ使っているところに出会っていないです。使い方によっては面白そうなことはできそうな感じがしますね。
ジェネレーターとは
必要な時に一つずつ値を順次生成するものジェネレーターと呼び、上記のクロージャーの例もその1つになります。今回は1ずつ増やすという簡単な実装でしたが、フィボナッチ数列を生成するジェネレーターなども応用すれば作ることができます。ぜひ興味がある人は作ってみてください。
ジェネレーターの注意点
当たり前と言えば当たり前ですが、変数名を変えてジェネレーターを実行するとそれぞれで値を保持するようになります。
func AddCounter() func() int {
var store int = 1
return func() int {
s := store
store++
return s
}
}
func main() {
f := AddCounter()
fmt.Println(f()) // 1
fmt.Println(f()) // 2
fmt.Println(f()) // 3
fmt.Println(f()) // 4
f2 := AddCounter()
fmt.Println(f2()) // 1
fmt.Println(f2()) // 2
fmt.Println(f2()) // 3
fmt.Println(f2()) // 4
}