W Go funkcje anonimowe (literaly funkcyjne, zamknięcia) pojawiły się w celu wsparcia stylu funkcyjnego, wywołań zwrotnych i zwięzłej enkapsulacji algorytmów. Są często używane do przetwarzania kolekcji, zadań asynchronicznych oraz przesyłania jako parametrów.
Bez funkcji anonimowych kod staje się nadmiarowy: każdą obróbkę trzeba wyodrębnić do osobnej nazwanej funkcji. Jednak pojawiają się pytania: jak działa "przechwytywanie" zmiennych, gdzie jest przechowywana pamięć, jakie cechy są przy deklaracji i przekazywaniu jako argumenty? Czy przechwycenie zmiennych jest chronione, jeśli są one zmieniane z zewnątrz? Częstym błędem jest niepoprawne przechwycenie zmiennej w pętli.
Funkcje anonimowe są deklarowane jako literały i mogą być przypisane do zmiennych lub używane od razu. Jeśli funkcja anonimowa odnosi się do zmiennych z zewnętrznego zakresu, są one "przechwytywane" i przechowywane przez czas życia zamknięcia. Jako parametr funkcji, funkcja anonimowa jest zazwyczaj przekazywana z typem func, zgodnym z sygnaturą. Większość problemów pojawia się podczas przechwytywania zmiennych w pętli - w tym przypadku, jeśli chcesz owinąć logikę w zamknięciu, koniecznie twórz nową zmienną wewnątrz pętli.
Przykład kodu:
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] }
Kluczowe cechy:
Co się stanie, gdy przechwycisz zmienną z zmienną wartością w pętli przez funkcję anonimową?
Wszystkie zamknięcia przechwycą tę samą zmienną i podczas wywołania funkcji po wyjściu z pętli otrzymasz tę samą wartość.
Przykład kodu:
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 }
Aby tego uniknąć, stwórz nową zmienną w ciele pętli:
for i := 0; i < 3; i++ { j := i a = append(a, func() { fmt.Println(j) }) // 0 1 2 }
Czy można używać funkcji anonimowych jako wartości typu interface{}?
Funkcje są zgodne tylko z interface{}, a nie z innymi interfejsami, nie można ich porównywać między sobą (oprócz nil). Jeśli przekażesz zamknięcie jako interface{}, można je wywołać tylko przez rzutowanie typu na sygnaturę func.
Czy funkcje anonimowe mogą być rekurencyjne?
Tak, ale tylko jeśli najpierw zadeklarujesz zmienną-nazwę dla zamknięcia, a następnie przypiszesz funkcję do niej samej.
Przykład kodu:
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
W pętli po liście callbacków programista podwiązuje obsługę jako zamknięcie z przechwyceniem zmiennej-iteratora. Wszystkie callbacki działają na nieodpowiedniej wartości, prowadząc do błędów.
Zalety:
Wady:
Wewnątrz pętli tworzą nową zmienną dla każdego zamknięcia, zapewniając poprawne przechwytywanie wartości i oczekiwane zachowanie.
Zalety:
Wady: