Pourquoi Go n'autorise-t-il pas les déclarations de fonctions imbriquées (fonctions à l'intérieur de fonctions)?

87

Edit: Si ce que je demandais n'était pas clair: quels sont les problèmes qui sont atténués en n'autorisant pas les déclarations de fonctions imbriquées?

Les lambdas fonctionnent comme prévu:

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

Cependant, la déclaration suivante dans une déclaration n'est pas autorisée:

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

Pour quelle raison les fonctions imbriquées ne sont-elles pas autorisées?

corazza
la source
hmm je ne sais pas si vous vouliez faire ça func main() { func (x int) int { return x+1; }(3) }
ymg
@YasirG. mais c'est aussi un lambda, n'est-ce pas? Je ne comprends pas votre commentaire ...
corazza
quelle fonctionnalité autorise l'activation du deuxième exemple dans la syntaxe, qui n'est pas prise en charge par le premier cas?
Not_a_Golfer
@yannbane c'est une expression lambda, je ne pense pas que vous puissiez déclarer une fonction dans une autre fonction comme JS. Donc, je dirais que votre meilleur choix est d'utiliser des lambdas.
ymg
@Not_a_Golfer: Une possibilité serait de l'implémenter comme le fait JavaScript, essentiellement attribuer une fonction à une variable est très différent de déclarer une fonction car le flux de contrôle affecte ces variables, tandis que les fonctions en JavaScript ne sont pas affectées. Cela signifie que vous pouvez appeler inc()le deuxième exemple avant la déclaration réelle. Mais! Je cherche des raisons, je ne connais pas grand chose à Go mais j'aimerais savoir quelle était la logique derrière cette règle.
corazza

Réponses:

54

Je pense qu'il y a 3 raisons pour lesquelles cette fonctionnalité évidente n'est pas autorisée

  1. Cela compliquerait légèrement le compilateur. Pour le moment, le compilateur sait que toutes les fonctions sont au niveau supérieur.
  2. Cela créerait une nouvelle classe d'erreur de programmeur - vous pourriez refactoriser quelque chose et imbriquer accidentellement certaines fonctions.
  3. Avoir une syntaxe différente pour les fonctions et les fermetures est une bonne chose. Faire une fermeture est potentiellement plus coûteux que créer une fonction, vous devez donc savoir que vous le faites.

Ce ne sont cependant que mes opinions - je n'ai pas vu de déclaration officielle des concepteurs de langage.

Nick Craig-Wood
la source
2
Pascal (au moins c'est l'incarnation de Delphi) les a juste et simples: les fonctions imbriquées se comportent comme des fonctions normales mais ont également accès aux variables dans la portée de leur fonction englobante. Je ne pense pas que ce soit difficile à mettre en œuvre. D'un autre côté, après avoir écrit beaucoup de code Delphi, je ne suis pas sûr d'avoir vraiment besoin de fonctions imbriquées: parfois elles se sentent chouettes mais elles ont tendance à faire sauter la fonction englobante, ce qui la rend difficilement lisible. L'accès aux arguments de leurs parents peut également rendre le programme difficile à lire puisque ces variables sont accessibles implicitement (non transmises comme paramètres formels).
kostix
1
Les fonctions locales sont excellentes comme étape de refactorisation intermédiaire sur votre façon d'extraire des méthodes. En c #, ils les ont rendus plus précieux une fois qu'ils ont introduit des fonctions locales statiques qui ne sont pas autorisées à capturer des variables à partir de la fonction englobante, vous êtes donc obligé de passer quoi que ce soit en paramètre. Les fonctions locales statiques font du point 3 un non-problème. Le point 2 est également un non-problème de mon point de vue.
Cosmin Sontu du
47

Bien sûr qu'ils le sont. Il vous suffit de les affecter à une variable:

func main() {
    inc := func(x int) int { return x+1; }
}
Matt Williamson
la source
4
à noter, vous ne pouvez pas appeler de telles fonctions (inc) de manière récursive.
Mohsin Kale
26

Foire aux questions (FAQ)

Pourquoi Go n'a-t-il pas la fonctionnalité X?

Chaque langue contient de nouvelles fonctionnalités et omet la fonctionnalité préférée de quelqu'un. Go a été conçu en tenant compte de la félicité de la programmation, de la vitesse de compilation, de l'orthogonalité des concepts et de la nécessité de prendre en charge des fonctionnalités telles que la concurrence et le ramassage des ordures. Votre fonctionnalité préférée peut manquer parce qu'elle ne convient pas, parce qu'elle affecte la vitesse de compilation ou la clarté de la conception, ou parce qu'elle rendrait le modèle système fondamental trop difficile.

Si la fonctionnalité X de Go vous dérange, veuillez nous pardonner et examiner les fonctionnalités de Go. Vous constaterez peut-être qu'ils compensent de manière intéressante le manque de X.

Qu'est-ce qui justifierait la complexité et le coût de l'ajout de fonctions imbriquées? Que voulez-vous faire que vous ne pouvez pas faire sans fonctions imbriquées? Etc.

peterSO
la source
19
Pour être honnête, je ne pense pas que quiconque ait démontré une complexité particulière que provoquerait l'autorisation de fonctions imbriquées. De plus, bien que je sois d'accord avec la philosophie citée, je ne suis pas sûr qu'il soit raisonnable de faire référence aux fonctions imbriquées comme une «fonctionnalité», autant que de faire référence à leur omission en tant que fonctionnalité. Connaissez-vous des complications que permettre des fonctions imbriquées? Je suppose qu'ils ne seraient que du sucre syntaxique pour les lambdas (je ne peux penser à aucun autre comportement raisonnable).
joshlf
Puisque go est compilé, la seule façon de faire cet AFAIK, créera une autre syntaxe pour définir les lambdas. Et je ne vois vraiment pas de cas d'utilisation pour cela. vous ne pouvez pas avoir une fonction statique dans une fonction statique créée en temps réel - que se passe-t-il si nous n'entrons pas le chemin de code spécifique qui définit la fonction?
Not_a_Golfer
Passez simplement l'interface lambda {} et tapez assert. Par exemple. f_lambda: = lambda (func () rval {}) ou quel que soit le prototype. La syntaxe func decl ne prend pas en charge cela, mais le langage le fait totalement.
BadZen
8

Les fonctions imbriquées sont autorisées dans Go. Il vous suffit de les affecter à des variables locales dans la fonction externe et de les appeler en utilisant ces variables.

Exemple:

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

Les fonctions internes sont souvent utiles pour les goroutines locales:

func outerFunction(...) {
    innerFunction := func(...) {
        ...
    }
    go innerFunction(...)
}
vthorsteinsson
la source
La fermeture en marche diffère par certains aspects de la fonction simple. Par exemple, vous ne pouvez pas appeler la fermeture de manière récursive.
Michał Zabielski
7
@ MichałZabielski: Vous pouvez l'appeler récursivement si vous le déclarez avant de le définir.
P Daddy
1

Il vous suffit de l'appeler immédiatement en ajoutant ()à la fin.

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

Edit: ne peut pas avoir de nom de fonction ... donc c'est juste une fonction lambda qui est appelée tout de suite:

func main() {
    func(x int) int { return x+1; }()
}
pseudo
la source
1
Euh qui n'est pas conforme à ce que l'on pourrait attendre d'une définition de fonction
corazza
1
@corazza Ah, j'ai raté le mot «déclaration» quand j'ai lu la question. Ma faute.
Nick
1
@corazza J'ai aussi foiré la syntaxe. Nécessaire pour supprimer le nom de la fonction. Donc, c'est essentiellement une fonction lambda qui est appelée immédiatement.
Nick