Warum erlaubt Go keine verschachtelten Funktionsdeklarationen (Funktionen innerhalb von Funktionen)?

87

Bearbeiten: Wenn nicht klar war, was ich gefragt habe: Welche Probleme werden gemildert, wenn verschachtelte Funktionsdeklarationen nicht zugelassen werden?

Lambdas arbeiten wie erwartet:

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

Die folgende Erklärung innerhalb einer Erklärung ist jedoch nicht zulässig:

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

Aus welchem ​​Grund sind verschachtelte Funktionen nicht zulässig?

Corazza
quelle
hmm ich weiß nicht, ob du das func main() { func (x int) int { return x+1; }(3) }
vorhast
@ YasirG. Aber das ist auch ein Lambda, nicht wahr? Ich verstehe Ihren Kommentar nicht ...
Corazza
Welche Funktionalität ermöglicht das Aktivieren des zweiten Beispiels in der Syntax, das im ersten Fall nicht unterstützt wird?
Not_a_Golfer
@yannbane es ist ein Lambda-Ausdruck, ich glaube nicht, dass Sie eine Funktion in einer anderen Funktion wie JS deklarieren können. Ich würde also sagen, dass Sie am besten Lambdas verwenden.
Ymg
@Not_a_Golfer: Eine Möglichkeit wäre, es so zu implementieren, wie es JavaScript tut. Das Zuweisen einer Funktion zu einer Variablen unterscheidet sich wesentlich vom Deklarieren einer Funktion, da der Kontrollfluss solche Variablen beeinflusst, während Funktionen in JavaScript nicht betroffen sind. Das heißt, Sie können inc()das zweite Beispiel vor der eigentlichen Deklaration aufrufen . Aber! Ich suche nach Gründen, ich weiß nicht viel über Go, aber ich würde gerne erfahren, was die Logik hinter dieser Regel war.
Corazza

Antworten:

54

Ich denke, es gibt drei Gründe, warum diese offensichtliche Funktion nicht erlaubt ist

  1. Dies würde den Compiler etwas komplizieren. Im Moment weiß der Compiler, dass sich alle Funktionen auf der obersten Ebene befinden.
  2. Es würde eine neue Klasse von Programmiererfehlern verursachen - Sie könnten etwas umgestalten und versehentlich einige Funktionen verschachteln.
  3. Eine andere Syntax für Funktionen und Abschlüsse ist eine gute Sache. Das Erstellen eines Verschlusses ist möglicherweise teurer als das Erstellen einer Funktion. Sie sollten also wissen, dass Sie dies tun.

Dies sind jedoch nur meine Meinungen - ich habe keine offizielle Erklärung der Sprachdesigner gesehen.

Nick Craig-Wood
quelle
2
Pascal (zumindest die Delphi-Inkarnation) hat sie richtig und einfach gemacht: Die verschachtelten Funktionen verhalten sich wie normale Funktionen, haben aber auch Zugriff auf die Variablen im Bereich ihrer umschließenden Funktion. Ich denke nicht, dass diese schwer umzusetzen sind. Andererseits bin ich mir nicht sicher, ob ich dringend verschachtelte Funktionen benötige, nachdem ich viel Delphi-Code geschrieben habe: Gelegentlich fühlen sie sich geschickt an, aber sie neigen dazu, die umschließende Funktion zu sprengen, was sie kaum lesbar macht. Auch der Zugriff auf die Argumente ihrer Eltern kann das Lesen des Programms erschweren, da implizit auf diese Variablen zugegriffen wird (nicht als formale Parameter übergeben).
Kostix
1
Lokale Funktionen eignen sich hervorragend als Zwischenschritt für das Refactoring auf dem Weg zum Extrahieren von Methoden. In c # haben sie diese wertvoller gemacht, nachdem sie statische lokale Funktionen eingeführt haben, die keine Variablen aus der umschließenden Funktion erfassen dürfen, sodass Sie gezwungen sind, alles als Parameter zu übergeben. Statische lokale Funktionen machen Punkt 3 zu einem Nicht-Problem. Punkt 2 ist aus meiner Sicht ebenfalls kein Thema.
Cosmin Sontu
46

Sicher sind sie. Sie müssen sie nur einer Variablen zuweisen:

func main() {
    inc := func(x int) int { return x+1; }
}
Matt Williamson
quelle
4
Es ist erwähnenswert, dass Sie solche Funktionen (inc) nicht rekursiv aufrufen können.
Mohsin Kale
26

Häufig gestellte Fragen (FAQ)

Warum hat Go kein Feature X?

Jede Sprache enthält neuartige Merkmale und lässt das Lieblingsmerkmal einer Person aus. Go wurde mit Blick auf die Glückseligkeit der Programmierung, die Geschwindigkeit der Kompilierung, die Orthogonalität der Konzepte und die Notwendigkeit entwickelt, Funktionen wie Parallelität und Speicherbereinigung zu unterstützen. Ihre Lieblingsfunktion fehlt möglicherweise, weil sie nicht passt, weil sie die Kompilierungsgeschwindigkeit oder die Klarheit des Designs beeinträchtigt oder weil dies das grundlegende Systemmodell zu schwierig machen würde.

Wenn es Sie stört, dass Go Feature X fehlt, verzeihen Sie uns bitte und untersuchen Sie die Funktionen von Go. Sie werden vielleicht feststellen, dass sie das Fehlen von X auf interessante Weise kompensieren.

Was würde die Komplexität und die Kosten des Hinzufügens verschachtelter Funktionen rechtfertigen? Was möchten Sie tun, was Sie ohne verschachtelte Funktionen nicht tun können? Und so weiter.

peterSO
quelle
19
Um fair zu sein, ich glaube nicht, dass irgendjemand eine besondere Komplexität gezeigt hat, die das Zulassen verschachtelter Funktionen verursachen würde. Obwohl ich der zitierten Philosophie zustimme, bin ich mir nicht sicher, ob es vernünftig ist, verschachtelte Funktionen als "Merkmal" zu bezeichnen, sondern vielmehr, dass sie als Merkmal weggelassen werden. Kennen Sie Komplikationen, die das Zulassen verschachtelter Funktionen ermöglichen würde? Ich gehe davon aus, dass es sich nur um syntaktischen Zucker für Lambdas handelt (mir fällt kein anderes vernünftiges Verhalten ein).
Joshlf
Da go kompiliert ist, wird nur mit dieser AFAIK eine andere Syntax zum Definieren von Lambdas erstellt. Und dafür sehe ich wirklich keinen Anwendungsfall. Sie können keine statische Funktion innerhalb einer statischen Funktion in Echtzeit erstellen lassen. Was ist, wenn wir nicht den spezifischen Codepfad eingeben, der die Funktion definiert?
Not_a_Golfer
Übergeben Sie einfach die Lambda-Schnittstelle {} und geben Sie assert ein. Z.B. f_lambda: = lambda (func () rval {}) oder was auch immer der Prototyp sein würde. Die func-Deklarationssyntax unterstützt dies nicht, die Sprache jedoch vollständig.
BadZen
8

Verschachtelte Funktionen sind in Go zulässig. Sie müssen sie nur lokalen Variablen innerhalb der äußeren Funktion zuweisen und sie mit diesen Variablen aufrufen.

Beispiel:

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")

Innere Funktionen sind für lokale Goroutinen oft nützlich:

func outerFunction(...) {
    innerFunction := func(...) {
        ...
    }
    go innerFunction(...)
}
vthorsteinsson
quelle
Closure in Go unterscheidet sich in einigen Aspekten von der einfachen Funktion. Beispielsweise können Sie den Abschluss nicht rekursiv aufrufen.
Michał Zabielski
7
@ MichałZabielski: Sie können es rekursiv aufrufen, wenn Sie es deklarieren, bevor Sie es definieren.
P Daddy
1

Sie müssen es nur sofort aufrufen, indem Sie ()es am Ende hinzufügen .

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

Bearbeiten: kann keinen Funktionsnamen haben ... es ist also nur eine Lambda-Funktion, die sofort aufgerufen wird:

func main() {
    func(x int) int { return x+1; }()
}
Nick
quelle
1
Ähh, das entspricht nicht dem, was man von einer Funktionsdefinition erwarten würde
Corazza
1
@corazza Ah, ich habe das Wort "Erklärung" verpasst, als ich die Frage gelesen habe. Mein Fehler.
Nick
1
@corazza Auch ich habe die Syntax auch vermasselt. Muss den Funktionsnamen entfernen. Es ist also im Grunde eine Lambda-Funktion, die sofort aufgerufen wird.
Nick