Quali sono i problemi che vengono mitigati non consentendo dichiarazioni di funzioni annidate in Go?


87

Lambda funziona come previsto:

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

Tuttavia, la seguente dichiarazione all'interno di una dichiarazione non è consentita:

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

Per quale motivo le funzioni annidate non sono consentite?


hmm non so se intendevi farlo func main() { func (x int) int { return x+1; }(3) }
ymg

@YasirG. ma anche quella è una lambda, no? Non ho ricevuto il tuo commento ...
corazza

quale funzionalità consentirà l'abilitazione del secondo esempio nella sintassi, che non è supportata dal primo caso?
Not_a_Golfer

@yannbane è un'espressione lambda, non penso che tu possa dichiarare una funzione all'interno di un'altra funzione come JS. Quindi direi che la soluzione migliore è usare lambda.
ymg

@Not_a_Golfer: Una possibilità sarebbe quella di implementarlo come fa JavaScript, essenzialmente l'assegnazione di una funzione a una variabile è molto diversa dalla dichiarazione di una funzione perché il flusso di controllo influisce su tali variabili, mentre le funzioni in JavaScript non vengono influenzate. Ciò significa che puoi chiamare inc()il secondo esempio prima della dichiarazione effettiva. Ma! Cerco ragioni, non so molto di Go ma vorrei sapere qual era la logica alla base di questa regola.
corazza

Risposte:


54

Penso che ci siano 3 ragioni per cui questa caratteristica ovvia non è consentita

  1. Complicherebbe leggermente il compilatore. Al momento il compilatore sa che tutte le funzioni sono al livello più alto.
  2. Creerebbe una nuova classe di errori del programmatore: potresti rifattorizzare qualcosa e annidare accidentalmente alcune funzioni.
  3. Avere una sintassi diversa per le funzioni e le chiusure è una buona cosa. Fare una chiusura è potenzialmente più costoso che realizzare una funzione, quindi dovresti sapere che lo stai facendo.

Queste sono solo le mie opinioni però - non ho visto un pronunciamento ufficiale dei progettisti del linguaggio.


2
Pascal (almeno è l'incarnazione di Delphi) le ha fatte giuste e semplici: le funzioni annidate si comportano come normali ma hanno anche accesso alle variabili nell'ambito della loro funzione che le racchiude. Non credo che siano difficili da implementare. D'altra parte, avendo scritto molto codice Delphi, non sono sicuro di aver bisogno di funzioni annidate: a volte si sentono eleganti ma tendono a far saltare la funzione che lo racchiude rendendolo difficilmente leggibile. Anche l'accesso agli argomenti dei loro genitori può rendere il programma difficile da leggere poiché a queste variabili si accede implicitamente (non passate come parametri formali).
kostix

1
le funzioni locali sono ottime come passaggio intermedio di refactoring per estrarre i metodi. In c # le hanno rese più preziose una volta introdotte funzioni locali statiche che non sono autorizzate a catturare variabili dalla funzione che racchiude, quindi sei costretto a passare qualsiasi cosa come parametro. Le funzioni locali statiche rendono il punto 3 un non problema. Anche il punto 2 è un non problema dal mio punto di vista.
Cosmin Sontu

47

Certo che lo sono. Devi solo assegnarli a una variabile:

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

5
degno di nota, non è possibile chiamare ricorsivamente tali funzioni (inc).
Mohsin Kale

1
per chiamarlo in modo ricorsivo, è necessario inoltrare la dichiarazionevar inc func(int) int
Izana

28

Domande frequenti (FAQ)

Perché Go non ha la funzione X?

Ogni lingua contiene nuove funzionalità e omette la caratteristica preferita di qualcuno. Go è stato progettato con un occhio alla felicità della programmazione, alla velocità di compilazione, all'ortogonalità dei concetti e alla necessità di supportare funzionalità come la concorrenza e la raccolta dei rifiuti. La tua funzione preferita potrebbe mancare perché non si adatta, perché influisce sulla velocità di compilazione o sulla chiarezza del design o perché renderebbe troppo difficile il modello di sistema fondamentale.

Se ti dà fastidio il fatto che Go non abbia la funzione X, perdonaci e analizza le funzionalità di Go. Potresti scoprire che compensano in modi interessanti la mancanza di X.

Cosa giustificherebbe la complessità e il costo dell'aggiunta di funzioni annidate? Cosa vuoi fare che non puoi fare senza funzioni annidate? Eccetera.


19
Per essere onesti, non credo che nessuno abbia dimostrato alcuna complessità particolare che consentirebbe di utilizzare funzioni annidate. Inoltre, anche se sono d'accordo con la filosofia citata, non sono sicuro che sia ragionevole riferirsi a funzioni annidate come una "caratteristica", tanto quanto riferirsi alla loro omissione come caratteristica. Conosci delle complicazioni che consentirebbero le funzioni annidate? Suppongo che sarebbero solo zucchero sintattico per lambda (non riesco a pensare a nessun altro comportamento ragionevole).
joshlf

Poiché go è compilato, l'unico modo per farlo, AFAIK, creerà un'altra sintassi per la definizione di lambda. E davvero non vedo un caso d'uso per questo. non puoi avere una funzione statica all'interno di una funzione statica creata in tempo reale - cosa succede se non inseriamo il percorso di codice specifico che definisce la funzione?
Not_a_Golfer

Passa semplicemente l'interfaccia lambda {} e digita assert. Per esempio. f_lambda: = lambda (func () rval {}) o qualunque sarebbe il prototipo. La sintassi func decl non lo supporta ma il linguaggio lo fa totalmente.
BadZen


8

Le funzioni annidate sono consentite in Go. Hai solo bisogno di assegnarli a variabili locali all'interno della funzione esterna e chiamarli usando quelle variabili.

Esempio:

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

Le funzioni interne sono spesso utili per i goroutine locali:

func outerFunction(...) {
    innerFunction := func(...) {
        ...
    }
    go innerFunction(...)
}

La chiusura in go differisce per alcuni aspetti dalla semplice funzione. Ad esempio, non è possibile chiamare la chiusura in modo ricorsivo.
Michał Zabielski

7
@ MichałZabielski: Puoi chiamarlo ricorsivamente se lo dichiari prima di definirlo.
P Daddy

1

Devi solo chiamarlo immediatamente aggiungendo ()alla fine.

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

Modifica: non può avere il nome della funzione ... quindi è solo una funzione lambda che viene chiamata subito:

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

1
Uhh questo non è conforme a ciò che ci si aspetterebbe da una definizione di funzione
corazza

1
@ corazza Ah, mi è sfuggita la parola "dichiarazione" quando ho letto la domanda. Colpa mia.
Nick

1
@corazza Inoltre, ho sbagliato anche la sintassi. Necessario per rimuovere il nome della funzione. Quindi, è fondamentalmente una funzione lambda che viene chiamata immediatamente.
Nick
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.