不允许在Go中嵌套函数声明可以缓解哪些问题?

131

Lambdas按预期工作:

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

然而,声明内部的以下声明是不允许的:

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

为什么不允许嵌套函数?


1
这个解决方案可以解决的一个问题是递归嵌套函数,详见 https://github.com/golang/go/issues/226 - Peter Dotchev
1
询问语言设计决策的“为什么”是不适合讨论的,因为这只有语言设计者才能回答的观点。当然,在这里询问如何绕过这些限制是适合讨论的。 - Jonathan Hall
1
@corazza:不,它们并非无法沟通。它们只是离题了。 - Jonathan Hall
1
@JonathanHall 了解设计相关事物的原因对开发人员来说肯定是有帮助的,尤其是在如今几乎所有人都使用多种语言进行工作的时候。我不明白这为什么是离题的。 - manisar
1
@JonathanHall 谢谢,是的,链接中解释了。但是如果有官方答案的话,这样的问题和答案对应应该不会被视为离题。提问者在提问时不知道是否有官方答案(否则他们根本不会提问),所以我想这个问题应该是允许的。其次 - 这只是我的看法 - 即使没有官方答案,将这样一个与编程相关的问题标记为“离题”或将其关闭为“基于观点”,我们将剥夺开发人员获得真正有用的信息,这是我们希望从 SO 获取的。 - manisar
显示剩余21条评论
5个回答

75

我认为有三个原因导致这个明显的特性不被允许

  1. 这会稍微复杂化编译器。目前,编译器知道所有函数都在顶层。
  2. 这将产生一种新的程序员错误类型 - 你可能会重构某些东西并不小心嵌套一些函数。
  3. 对函数和闭包采用不同的语法是一件好事。制作闭包的成本可能比制作函数更高,因此你应该知道你正在做什么。

不过这只是我的看法,我还没有看到语言设计者的官方声明。


6
Pascal(至少其Delphi版本)正确而简单地实现了嵌套函数:嵌套函数的行为与常规函数相同,但也可以访问其封闭函数作用域中的变量。我认为这些并不难实现。另一方面,尽管我已经编写了大量的Delphi代码,但我不确定我非常需要嵌套函数:有时它们感觉很巧妙,但它们往往会扩大封闭函数,使其几乎无法阅读。此外,访问其父级函数的参数可能会使程序难以阅读,因为这些变量是隐式访问的(而不是作为形式参数传递)。 - kostix
1
本地函数是在提取方法的过程中作为中间重构步骤非常好的选择。在C#中,引入了静态本地函数,使得它们更加有价值,因为它们不允许捕获封闭函数的变量,所以你必须将任何东西作为参数传递。静态本地函数使得第三点成为无关紧要的问题。从我的角度来看,第二点也不是问题。 - Cosmin Sontu
通过支持嵌套结构声明,Go语言使得点1和点2无效。结构体可以在函数内部定义,这意味着类型的作用域和顶层与其他层次之间的区别变得毫无意义。这也使得在重构结构体时出现了一种新的程序员错误类型。 - Tenders McChiken

29

常见问题解答(FAQ)

为什么Go不支持X特性?

每个语言都包含新颖的特性并省略了某些人喜欢的特性。Go是注重编程体验、编译速度、概念正交性以及需要支持并发和垃圾回收等特性而设计的。你喜欢的特性可能因为它不适合,影响编译速度或者设计上的清晰度,或者会使得基本系统模型过于复杂而被省略。

如果你对于Go缺少X特性感到烦恼,请原谅我们并且探索下Go所拥有的特性。你可能会发现它们以有趣的方式弥补了缺少X的不便。

什么情况下会证明增加嵌套函数的复杂性和费用是合理的?你想做什么而没有嵌套函数实现不了?等等。


22
公平地说,我认为没有人展示出允许嵌套函数会导致任何特别的复杂性。此外,虽然我同意引用的哲学,但我不确定将嵌套函数称为“特点”是否合理,更多地是将它们的缺失视为一种特点。你知道允许嵌套函数会带来什么样的复杂性吗?我假设它们只是lambda表达式的语法糖(我想不到其他合理的行为)。 - joshlf
由于Go是编译型语言,据我所知,唯一的方法是创建另一种语法来定义lambda表达式。而且我真的看不出有什么用例。你不能在实时创建的静态函数中再创建一个静态函数 - 如果我们没有进入定义该函数的特定代码路径会怎样? - Not_a_Golfer
只需传入 lambda 接口{} 和类型断言。例如,f_lambda := lambda(func() rval {}) 或任何原型都可以。函数声明语法不支持此操作,但该语言完全支持。 - BadZen
对于像二叉树遍历这样的事情来说,定义递归子函数来对单个节点进行递归,处理其左右分支是非常常见的。基本上需要为递归命名这个子函数,对吧? - undefined

21

这个答案只是重复了问题本身已经表达的内容。 - Roland Illig

13

在 Go 语言中允许嵌套函数。你只需在外部函数内将其分配给局部变量,并使用这些变量调用它们。

例如:

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

内部函数常常用于本地goroutine:

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

1
Go语言中的闭包与普通函数在某些方面有所不同。例如,您不能递归调用闭包。 - Michał Zabielski
10
如果你在定义之前声明它,就可以递归调用它。 - P Daddy
2
这个答案只是重复了问题已经表达的内容。 - Roland Illig

-1

你只需要在结尾添加()即可立即调用它。

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

编辑:不能有函数名称...所以它只是一个立即调用的lambda函数:

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

1
哦,那不符合函数定义的预期。 - corazza
1
@corazza 啊,我在阅读问题时错过了“声明”这个词。我的错。 - Nick
1
@corazza 另外,我也搞砸了语法。需要删除函数名称。因此,它基本上是一个立即调用的 lambda 函数。 - Nick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接