Go言語のクロージャー(Closure)について扱います。
クロージャーは無名関数のことを差すこともありますが、変数のスコープをその範囲の中に制限することができるという性質があります。
変数のスコープ
クロージャーを扱う前に、変数を参照できる範囲のスコープについて見ていきましょう。
次のようなコードを見てみます。
package main
import "fmt"
var x int
func increment() {
fmt.Println("Hello Golang!")
x++
}
func main() {
fmt.Println(x)
x++
fmt.Println(x)
increment()
fmt.Println(x)
}
int型の変数xを宣言しています。これは0が割り当てられます。
increment()関数を宣言して、文字列の出力と、xの値を1増やす処理をしています。
main()関数の中でincrement()を呼び出したり、x++の処理をしています。この時の変数xのスコープを確認しましょう。
実行するとこうなります。
0
1
Hello Golang!
2
宣言したxの値を順に増加させているのがわかります。どの関数からも変数を参照できている形になっています。
このコードを次のように変更してみましょう。
package main
import "fmt"
func increment() {
fmt.Println("Hello Golang!")
x++
}
func main() {
var x int
fmt.Println(x)
x++
fmt.Println(x)
increment()
fmt.Println(x)
}
変数xの宣言を、main()関数の中で宣言しなおしています。それ以外のコードは先ほどと同じです。
ただし、これは実行してもエラーになってしまいます。
undefined: x
この場合、increment()関数内のx++はundefined: x となり、参照できない変数を見ていることになっています。
変数xはmain()関数の中でしか参照することができない形です。
クロージャー
では、クロージャーを見て行きましょう。変数にスコープがあることは、上のコードでわかると思います。
その変数のスコープをクロージャーを使えば、その範囲の中に制限することができます。
実際のコードで見ていきましょう。
package main
import "fmt"
func hello() {
fmt.Println("Hello Golang!")
}
func main() {
var x int
fmt.Println(x)
x++
{
y := 2020
fmt.Println(y)
}
// fmt.Println(y)
fmt.Println(x)
hello()
}
これは先ほどのコードを改変しています。
main()関数の中で、変数xを宣言しています。それをx++でインクリメントしています。
波括弧{}で括って変数yを宣言してPrintln()で出力しています。
実行すると次のようになります。
0
2020
1
Hello Golang!
xについて、インクリメントされているのがわかりますが、ここでyは、波括弧の中でしか変数の参照ができないことに注意しましょう。
コメントアウトで、波括弧の外でyを出力するコードを書いていますが、これを実行した場合はエラーになります。
このように、波括弧ブロックの中の変数はその中でしか利用できません。
クロージャーはこのような性質を利用する無名関数になります。
具体的なコードで書いてみます。
package main
import "fmt"
func increment() func() int {
var x int
return func() int {
x++
return x
}
}
func main() {
y1 := increment()
y2 := increment()
fmt.Println(y1())
fmt.Println(y1())
fmt.Println(y1())
fmt.Println(y1())
fmt.Println(y2())
fmt.Println(y2())
fmt.Println(y2())
}
increment()関数を定義していますが、戻り値にint型の関数func()をとっています。この無名関数がx++のインクリメント処理を返すことになっています。
これをmain()関数の中で、increment()関数を呼び出して実行しています。
戻り値に関数をとる処理はこちらで扱いました。
順にy1、y2を連続して実行しています。
実行結果は以下のようになります。
1
2
3
4
1
2
3
実行するたびに、値に1が加わっている処理を確認することができます。
y2のように、別で呼び出した部分では処理が最初から行われているのがわかります。これで変数がその範囲内に制限されて処理されているということがわかります。
これがクロージャーの動きですが、ちょっと難しい部類の話になりますかね。
最後に
Go言語の無名関数の性質のひとつのクロージャーについて見てきました。
クロージャーは、変数のスコープを制限する役割をもつものです。ブロックの処理の内側でのみ変数を参照できるというものです。