Dlaczego Go nie zezwala na deklaracje funkcji zagnieżdżonych (funkcje wewnątrz funkcji)?

87

Edycja: Jeśli nie było jasne, o co pytałem: jakie problemy można złagodzić, nie zezwalając na zagnieżdżone deklaracje funkcji?

Lambdy działają zgodnie z oczekiwaniami:

func main() {
    inc := func(x int) int { return x+1; }
}

Jednak następująca deklaracja w deklaracji nie jest dozwolona:

func main() {
    func inc(x int) int { return x+1; }
}

Z jakiego powodu funkcje zagnieżdżone są niedozwolone?

corazza
źródło
hmm, nie wiem, czy zamierzałeś to zrobić func main() { func (x int) int { return x+1; }(3) }
ymg
@YasirG. ale to też jest lambda, prawda? Nie dostaję twojego komentarza ...
corazza,
jaką funkcjonalność pozwoli włączenie drugiego przykładu w składni, czego nie obsługuje pierwszy przypadek?
Not_a_Golfer
@yannbane to wyrażenie lambda, nie sądzę, aby można było zadeklarować funkcję wewnątrz innej funkcji, takiej jak JS. Więc powiedziałbym, że najlepiej pasujesz do lambd.
ymg
@Not_a_Golfer: Jedną z możliwości byłoby zaimplementowanie go tak, jak robi to JavaScript, zasadniczo przypisanie funkcji do zmiennej jest zupełnie inne niż deklarowanie funkcji, ponieważ przepływ kontroli wpływa na takie zmienne, podczas gdy funkcje w JavaScript nie ulegają zmianie. Oznacza to, że możesz wywołać inc()drugi przykład przed faktyczną deklaracją. Ale! Szukam powodów, niewiele wiem o Go, ale chciałbym się dowiedzieć, jaka była logika tej zasady.
corazza

Odpowiedzi:

54

Myślę, że są 3 powody, dla których ta oczywista funkcja jest niedozwolona

  1. To trochę skomplikowałoby kompilator. W tej chwili kompilator wie, że wszystkie funkcje są na najwyższym poziomie.
  2. Spowodowałoby to nową klasę błędu programisty - można by coś zmienić i przypadkowo zagnieździć niektóre funkcje.
  3. Posiadanie innej składni dla funkcji i domknięć jest dobrą rzeczą. Wykonanie zamknięcia jest potencjalnie droższe niż wykonanie funkcji, więc powinieneś wiedzieć, że to robisz.

To tylko moje opinie - nie widziałem oficjalnego oświadczenia projektantów języka.

Nick Craig-Wood
źródło
2
Pascal (przynajmniej jest to wcielenie Delphi) ma rację i jest prosty: zagnieżdżone funkcje zachowują się jak zwykłe, ale mają również dostęp do zmiennych w zakresie ich funkcji otaczającej. Nie sądzę, aby były one trudne do wdrożenia. Z drugiej strony, po napisaniu dużej ilości kodu w Delphi, nie jestem pewien, czy bardzo potrzebuję zagnieżdżonych funkcji: czasami wydają się fajne, ale mają tendencję do niszczenia funkcji zamykającej, co czyni ją trudną do odczytania. Również dostęp do argumentów ich rodziców może utrudnić odczytanie programu, ponieważ te zmienne są dostępne niejawnie (nie są przekazywane jako parametry formalne).
kostix
1
funkcje lokalne świetnie sprawdzają się jako pośredni etap refaktoryzacji na drodze do wyodrębniania metod. W języku C # uczynili je bardziej wartościowymi, gdy wprowadzili statyczne funkcje lokalne, które nie mogą przechwytywać zmiennych z funkcji obejmującej, więc jesteś zmuszony do przekazywania czegokolwiek jako parametru. Statyczne funkcje lokalne sprawiają, że punkt 3 nie jest problemem. Punkt 2 również nie jest problemem z mojego punktu widzenia.
Cosmin Sontu,
47

Jasne, że tak. Wystarczy przypisać je do zmiennej:

func main() {
    inc := func(x int) int { return x+1; }
}
Matt Williamson
źródło
4
Warto zauważyć, że nie można rekurencyjnie wywoływać takich funkcji (inc).
Mohsin Kale
26

Często zadawane pytania (FAQ)

Dlaczego Go nie ma funkcji X?

Każdy język zawiera nowe funkcje i pomija czyjąś ulubioną funkcję. Go został zaprojektowany z myślą o łatwości programowania, szybkości kompilacji, ortogonalności koncepcji oraz potrzebie obsługi takich funkcji, jak współbieżność i usuwanie elementów bezużytecznych. Może brakować Twojej ulubionej funkcji, ponieważ nie pasuje, ponieważ wpływa na szybkość kompilacji lub przejrzystość projektu, lub ponieważ może utrudnić podstawowy model systemu.

Jeśli przeszkadza Ci to, że w Go brakuje funkcji X, wybacz nam i zbadaj funkcje, które ma Go. Może się okazać, że w interesujący sposób rekompensują one brak X.

Co uzasadniałoby złożoność i koszt dodawania funkcji zagnieżdżonych? Co chcesz zrobić, czego nie możesz zrobić bez funkcji zagnieżdżonych? I tak dalej.

peterSO
źródło
19
Szczerze mówiąc, nie sądzę, aby ktokolwiek wykazał jakąś szczególną złożoność, którą spowodowałoby zezwolenie na funkcje zagnieżdżone. Poza tym, chociaż zgadzam się z przytoczoną filozofią, nie jestem pewien, czy rozsądne jest nazywanie funkcji zagnieżdżonych jako „funkcji”, a nawet odnoszenie się do ich pominięcia jako funkcji. Czy znasz jakieś komplikacje, na które pozwoliłoby zezwolenie na funkcje zagnieżdżone? Zakładam, że byłyby po prostu cukrem syntaktycznym dla lambd (nie przychodzi mi do głowy żadne inne rozsądne zachowanie).
joshlf
Ponieważ go jest kompilowane, jedynym sposobem na zrobienie tego AFAIK jest utworzenie innej składni do definiowania lambd. I naprawdę nie widzę dla tego przypadku użycia. nie możesz mieć statycznej funkcji w ramach statycznej funkcji utworzonej w czasie rzeczywistym - co, jeśli nie wprowadzimy konkretnej ścieżki kodu, która definiuje funkcję?
Not_a_Golfer
Po prostu podaj interfejs lambda {} i wpisz assert. Na przykład. f_lambda: = lambda (func () rval {}) lub jakikolwiek byłby prototyp. Składnia func decl tego nie obsługuje, ale język całkowicie tak.
BadZen
8

Zagnieżdżone funkcje są dozwolone w Go. Wystarczy przypisać je do zmiennych lokalnych w funkcji zewnętrznej i wywołać je przy użyciu tych zmiennych.

Przykład:

func outerFunction(iterations int, s1, s2 string) int {
    someState := 0
    innerFunction := func(param string) int {
        // Could have another nested function here!
        totalLength := 0
        // Note that the iterations parameter is available
        // in the inner function (closure)
        for i := 0; i < iterations; i++) {
            totalLength += len(param)
        }
        return totalLength
    }
    // Now we can call innerFunction() freely
    someState = innerFunction(s1)
    someState += innerFunction(s2)
    return someState
}
myVar := outerFunction(100, "blah", "meh")

Funkcje wewnętrzne są często przydatne w przypadku lokalnych gorutyn:

func outerFunction(...) {
    innerFunction := func(...) {
        ...
    }
    go innerFunction(...)
}
vthorsteinsson
źródło
Zamknięcie w ruchu różni się w niektórych aspektach od zwykłej funkcji. Na przykład nie można rekurencyjnie wywoływać zamknięcia.
Michał Zabielski
7
@ MichałZabielski: Możesz to wywołać rekurencyjnie, jeśli zadeklarujesz to przed zdefiniowaniem.
P Daddy
1

Musisz tylko natychmiast to wywołać, dodając ()na końcu.

func main() {
    func inc(x int) int { return x+1; }()
}

Edycja: nie może mieć nazwy funkcji ... więc jest to tylko funkcja lambda, która jest wywoływana od razu:

func main() {
    func(x int) int { return x+1; }()
}
Nacięcie
źródło
1
Uhh, to nie jest zgodne z tym, czego można by się spodziewać po definicji funkcji
corazza
1
@corazza Ach, kiedy czytałem pytanie, przegapiłem słowo „deklaracja”. Mój błąd.
Nick
1
@corazza Również schrzaniłem składnię. Potrzebne do usunięcia nazwy funkcji. Jest to więc w zasadzie funkcja lambda, która jest wywoływana natychmiast.
Nick