Goでは、匿名関数(function literals、closures)が関数型スタイル、コールバック、およびアルゴリズムの簡潔なカプセル化をサポートするために登場しました。これらは、コレクションの処理、非同期タスク、およびパラメータとしての受け渡しに頻繁に使用されます。
匿名関数がなければ、コードは冗長になり、すべての処理を別々の名前付き関数に抽出する必要があります。しかし、それに伴って「変数のキャプチャ」がどのように機能するか、メモリはどこに保存されるか、引数としての宣言と渡し方にはどのような特性があるか、といった問題が生じます。外部から変数が変更された場合、キャプチャが保護されているのか?よくある誤りは、ループ内での変数の不正なキャプチャです。
匿名関数はリテラルとして宣言され、変数に代入することも、すぐに使用することもできます。匿名関数が外部の変数にアクセスすると、それらは「キャプチャ」され、クロージャの寿命にわたって保存されます。関数のパラメータとしては、匿名関数は通常、シグネチャに一致するfunc型で渡されます。変数のキャプチャに関する問題は、ループの変数をキャプチャする際に発生します。この場合、クロージャでロジックをラップする必要がある場合は、必ずループ内に新しい変数を作成してください。
コードの例:
func operate(nums []int, op func(int) int) []int { res := make([]int, len(nums)) for i, n := range nums { res[i] = op(n) } return res } func main() { arr := []int{1, 2, 3} out := operate(arr, func(x int) int {return x * x}) fmt.Println(out) // [1 4 9] }
主な特徴:
ループ内で変更可能な値の変数を匿名関数でキャプチャすると、どうなりますか?
すべてのクロージャは同じ変数をキャプチャし、ループを抜けた後に関数を呼び出すと、同じ値を得ることになります。
コードの例:
func main() { a := []func(){} for i := 0; i < 3; i++ { a = append(a, func() { fmt.Println(i) }) } for _, f := range a { f() } // 3 3 3 }
これを避けるには、ループの本体で新しい変数を作成してください:
for i := 0; i < 3; i++ { j := i a = append(a, func() { fmt.Println(j) }) // 0 1 2 }
匿名関数をinterface{}型の値として使用することはできますか?
関数はinterface{}にのみ互換性があり、他のインターフェースには互換性がありません。相互に比較することはできません(nilを除く)。クロージャをinterface{}として渡すと、funcシグネチャに型変換を通じてしか呼び出せません。
匿名関数は再帰的にできますか?
はい、しかし、最初にクロージャのための名前の変数を宣言し、それに関数を再代入する必要があります。
コードの例:
var fib func(n int) int fib = func(n int) int { if n < 2 { return n } return fib(n-1) + fib(n-2) } fmt.Println(fib(10)) // 55
コールバックのリストをループする中で、開発者は変数イテレータをキャプチャするクロージャとしてハンドラーをバインドします。すべてのコールバックが不適切な値で動作し、バグを引き起こします。
利点:
欠点:
ループ内で各クロージャのために新しい変数を作成し、値の適切なキャプチャと期待される動作を保証します。
利点:
欠点: