Por que o Go não permite declarações de funções aninhadas (funções dentro de funções)?

87

Edit: Se não ficou claro o que eu estava perguntando: quais são os problemas que são mitigados por não permitir declarações de funções aninhadas?

Lambdas funcionam conforme o esperado:

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

No entanto, a seguinte declaração dentro de uma declaração não é permitida:

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

Por que razão as funções aninhadas não são permitidas?

Corazza
fonte
hmm eu não sei se você pretendia fazer isso func main() { func (x int) int { return x+1; }(3) }
ymg
@YasirG. mas também é um lambda, não é? Não entendi seu comentário ...
corazza
qual funcionalidade permitirá a ativação do segundo exemplo na sintaxe, que não é suportada pelo primeiro caso?
Not_a_Golfer
@yannbane é uma expressão lambda, não acho que você pode declarar uma função dentro de outra função como JS. Então, eu diria que sua melhor opção é usar lambdas.
ymg
@Not_a_Golfer: Uma possibilidade seria implementá-lo da maneira que o JavaScript faz, essencialmente atribuir uma função a uma variável é muito diferente de declarar uma função porque o fluxo de controle afeta essas variáveis, enquanto as funções em JavaScript não são afetadas. Isso significa que você pode chamar inc()o segundo exemplo antes da declaração real. Mas! Estou procurando razões, não sei muito sobre Go, mas gostaria de saber qual era a lógica por trás dessa regra.
corazza

Respostas:

54

Acho que há três razões pelas quais esse recurso óbvio não é permitido

  1. Isso complicaria um pouco o compilador. No momento, o compilador sabe que todas as funções estão no nível superior.
  2. Isso causaria uma nova classe de erro do programador - você poderia refatorar algo e acidentalmente aninhar algumas funções.
  3. Ter uma sintaxe diferente para funções e encerramentos é uma coisa boa. Fazer um fechamento é potencialmente mais caro do que fazer uma função, portanto, você deve saber o que está fazendo.

Essas são apenas minhas opiniões - eu não vi um pronunciamento oficial dos designers da linguagem.

Nick Craig-Wood
fonte
2
Pascal (pelo menos é a encarnação do Delphi) acertou e foi simples: as funções aninhadas se comportam como regulares, mas também têm acesso às variáveis ​​no escopo de sua função envolvente. Não acho que sejam difíceis de implementar. Por outro lado, tendo escrito muito código Delphi, não tenho certeza se preciso urgentemente de funções aninhadas: às vezes elas parecem legais, mas tendem a estragar a função de fechamento, dificultando a leitura. Acessar também os argumentos de seus pais pode tornar o programa difícil de ler, pois essas variáveis ​​são acessadas implicitamente (não são passadas como parâmetros formais).
kostix
1
as funções locais são ótimas como uma etapa intermediária de refatoração em seu caminho para extrair métodos. Em c #, eles os tornaram mais valiosos, uma vez que introduziram funções locais estáticas que não têm permissão para capturar variáveis ​​da função envolvente, então você é forçado a passar qualquer coisa como parâmetro. As funções locais estáticas tornam o ponto 3 um problema. O ponto 2 também não é um problema do meu ponto de vista.
Cosmin Sontu
47

Claro que estão. Você só precisa atribuí-los a uma variável:

func main() {
    inc := func(x int) int { return x+1; }
}
Matt Williamson
fonte
4
vale a pena notar, você não pode chamar tais funções (inc) recursivamente.
Mohsin Kale
26

Perguntas frequentes (FAQ)

Por que Go não possui o recurso X?

Cada idioma contém novos recursos e omite o recurso favorito de alguém. Go foi projetado com foco na felicidade da programação, velocidade de compilação, ortogonalidade de conceitos e a necessidade de oferecer suporte a recursos como concorrência e coleta de lixo. Seu recurso favorito pode estar faltando porque não se encaixa, porque afeta a velocidade de compilação ou a clareza do design, ou porque tornaria o modelo fundamental do sistema muito difícil.

Se o incomoda que o Go não tenha o recurso X, perdoe-nos e investigue os recursos que o Go tem. Você pode descobrir que eles compensam de maneiras interessantes a falta de X.

O que justificaria a complexidade e o custo de adicionar funções aninhadas? O que você quer fazer que não pode fazer sem funções aninhadas? Et cetera.

PeterSO
fonte
19
Para ser justo, não acho que ninguém demonstrou qualquer complexidade particular que permitir funções aninhadas causaria. Além disso, embora concorde com a filosofia citada, não tenho certeza se é razoável referir-se a funções aninhadas como um "recurso", tanto quanto se referir à sua omissão como um recurso. Você sabe de alguma complicação que permitiria funções aninhadas? Estou assumindo que eles seriam apenas açúcar sintático para lambdas (não consigo pensar em nenhum outro comportamento razoável).
joshlf
Uma vez que go é compilado, a única maneira de fazer isso AFAIK, será criar outra sintaxe para definir lambdas. E eu realmente não vejo um caso de uso para isso. você não pode ter uma função estática dentro de uma função estática criada em tempo real - e se não inserirmos o caminho do código específico que define a função?
Not_a_Golfer
Basta passar a interface lambda {} e digitar assert. Por exemplo. f_lambda: = lambda (func () rval {}) ou qualquer que seja o protótipo. A sintaxe func decl não suporta isso, mas a linguagem suporta totalmente.
BadZen
8

Funções aninhadas são permitidas no Go. Você só precisa atribuí-los a variáveis ​​locais dentro da função externa e chamá-los usando essas variáveis.

Exemplo:

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

As funções internas costumam ser úteis para goroutines locais:

func outerFunction(...) {
    innerFunction := func(...) {
        ...
    }
    go innerFunction(...)
}
vthorsteinsson
fonte
O fechamento em go difere em alguns aspectos da função simples. Por exemplo, você não pode chamar o encerramento recursivamente.
Michał Zabielski
7
@ MichałZabielski: Você pode chamá-lo recursivamente se declarar antes de defini-lo.
P Daddy
1

Você apenas tem que chamá-lo imediatamente adicionando ()ao final.

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

Editar: não pode ter nome de função ... então é apenas uma função lambda sendo chamada imediatamente:

func main() {
    func(x int) int { return x+1; }()
}
usuario
fonte
1
Uhh, isso não está de acordo com o que se esperaria de uma definição de função
corazza
1
@corazza Ah, perdi a palavra "declaração" quando li a pergunta. Foi mal.
Nick
1
@corazza Além disso, eu estraguei a sintaxe também. Necessário para remover o nome da função. Portanto, é basicamente uma função lambda que é chamada imediatamente.
Nick