サーチ…
閉鎖の基礎
閉鎖とは、環境と一緒にとられる機能です。関数は通常、別の関数の中で定義された無名関数です。環境は、囲む関数の字句的範囲です(関数の字句的範囲の非常に基本的な考え方は、関数の中括弧の間に存在する範囲になります)。
func g() {
i := 0
f := func() { // anonymous function
fmt.Println("f called")
}
}
別の関数(例えばg
)内で定義されている無名関数( f
と言う)の中で、 f
とg
の両方のスコープに存在する変数にアクセスすることができます。しかし、クロージャの環境部分を構成するのはg
のスコープであり(関数部分はf
)、 g
のスコープ内の変数に加えられた変更はその値を保持します(つまり、 f
呼び出しの間に環境が存続します) 。
以下の関数を考えてみましょう:
func NaturalNumbers() func() int {
i := 0
f:= func() int { // f is the function part of closure
i++
return i
}
return f
}
上の定義では、 NaturalNumbers
はNaturalNumbers
が返す内部関数f
がNaturalNumbers
ます。 f
、 NaturalNumbers
のスコープ内で定義された変数i
にアクセスしています。
私たちはNaturalNumbers
からNaturalNumbers
ような新しい関数を取得します:
n := NaturalNumbers()
今、 n
はクロージャーです。関連する環境( NaturalNumbers
スコープ)も持つ関数( f
定義)です。
n
場合、環境部分には1つの変数のみが含まれますi
n
は関数なので、呼び出すことができます:
fmt.Println(n()) // 1
fmt.Println(n()) // 2
fmt.Println(n()) // 3
上の出力から明らかなように、 n
が呼び出されるたびに、 i
インクリメントします。 i
は0から開始し、 n
への各呼び出しはi++
実行しi++
。
i
の値はコール間で保持されます。つまり、閉鎖の一部である環境が存続します。
NaturalNumbers
再度呼び出すと、新しい関数が作成されて返されます。これは、 NaturalNumbers
内の新しいi
を初期化します。これは、新たに返された関数が、関数(まだf
)ではなく新しい環境(新たに初期化されたi
)で同じ部分を持つ別のクロージャを形成することを意味します。
o := NaturalNumbers()
fmt.Println(n()) // 4
fmt.Println(o()) // 1
fmt.Println(o()) // 2
fmt.Println(n()) // 5
n
とo
両方は、同じ機能部分(同じ動作を与える)を含むクロージャですが、異なる環境です。したがって、クロージャを使用すると、関数間で情報を保持するために使用できる永続的な環境に関数がアクセスできるようになります。
もう一つの例:
func multiples(i int) func() int {
var x int = 0
return func() int {
x++
// paramenter to multiples (here it is i) also forms
// a part of the environment, and is retained
return x * i
}
}
two := multiples(2)
fmt.Println(two(), two(), two()) // 2 4 6
fortyTwo := multiples(42)
fmt.Println(fortyTwo(), fortyTwo(), fortyTwo()) // 42 84 126