为什么Go不允许嵌套函数声明(函数内部的函数)?


87

编辑:如果不清楚我在问什么不允许嵌套函数声明可以缓解哪些问题?

Lambdas按预期工作:

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

但是,不允许在声明中使用以下声明:

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

出于什么原因不允许嵌套函数?


嗯,我不知道你是否打算这样做 func main() { func (x int) int { return x+1; }(3) }
ymg 2014年

@YasirG。但这也是lambda,不是吗?我没有收到您的评论……
corazza 2014年

启用语法中的第二个示例所允许的功能(第一种情况不支持)?
Not_a_Golfer 2014年

@yannbane这是一个lambda表达式,我认为您无法在JS之类的另一个函数中声明一个函数。因此,我说您最适合使用lambda。
ymg 2014年

@Not_a_Golfer:一种可能性是像JavaScript一样实现它,本质上将函数分配给变量与声明函数有很大不同,因为控制流会影响此类变量,而JavaScript中的函数不会受到影响。这意味着您可以inc()在实际声明之前在第二个示例中调用。但!我正在寻找原因,我对Go不太了解,但是我想了解该规则背后的逻辑。
corazza 2014年

Answers:


54

我认为有3个原因不允许使用此明显功能

  1. 这会使编译器稍微复杂化。目前,编译器知道所有功能都在顶层。
  2. 这将导致一类新的程序员错误-您可能重构某些内容并意外地嵌套了某些功能。
  3. 对函数和闭包使用不同的语法是一件好事。制作闭包可能比制作函数更昂贵,因此您应该知道自己在做。

但是,这些只是我的意见-我还没有看到语言设计师的官方声明。


2
Pascal(至少是Delphi的化身)使它们正确而简单:嵌套函数的行为就像常规函数一样,但也可以访问其封闭函数范围内的变量。我认为这些很难实施。另一方面,编写了很多Delphi代码后,我不确定我是否非常需要嵌套函数:有时它们会很漂亮,但它们倾向于破坏封闭的函数,使其难以读取。另外,访问其父母的参数可能会使程序难以阅读,因为这些变量是隐式访问的(不作为形式参数传递)。
kostix 2014年

1
在提取方法的过程中,本地函数非常适合作为中间重构步骤。在c#中,一旦引入了静态局部函数(不允许从封闭函数中捕获变量),则它们使这些函数更有价值,因此您被迫将任何内容作为参数传递。静态局部函子使第3点成为非问题。从我的角度来看,第二点也不是问题。
Cosmin Sontu

47

当然可以。您只需要将它们分配给变量:

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

4
值得注意的是,您不能递归调用此类函数(inc)。
Mohsin Kale

26

常见问题解答(FAQ)

为什么Go没有功能X?

每种语言都包含新颖的功能,并且会忽略某人喜欢的功能。Go的设计着眼于编程的实用性,编译速度,概念的正交性以及对支持诸如并发和垃圾收集等功能的需求。您最喜欢的功能可能会丢失,因为它不合适,因为它会影响编译速度或设计的清晰度,或者因为它将使基本系统模型变得太困难。

如果困扰您Go缺少功能X,请原谅我们并调查Go确实具有的功能。您可能会发现它们以有趣的方式补偿了X的缺失。

有什么理由证明添加嵌套函数的复杂性和费用?如果没有嵌套函数,您将要做什么呢?等等。


19
公平地说,我认为没有人展示允许嵌套函数会导致的任何特殊复杂性。另外,尽管我同意所引用的哲学,但我不确定将嵌套函数称为“功能”是否合理,以及将嵌套函数称为功能是不合理的。您是否知道允许嵌套函数会带来什么并发症?我假设它们只是lambda的语法糖(我想不到其他合理的行为)。
joshlf 2014年

由于go是经过编译的,因此执行此AFAIK的唯一方法将是创建用于定义lambda的另一种语法。而且我真的没有看到用例。您不能在实时创建的静态函数中拥有静态函数-如果我们不输入定义函数的特定代码路径怎么办?
Not_a_Golfer 2014年

只需传入lambda接口{}并键入assert。例如。f_lambda:= lambda(func()rval {})或任何原型。func decl语法不支持此语法,但该语言完全支持。
BadZen


8

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(...)
}

go的封闭在某些方面与普通功能有所不同。例如,您不能递归调用闭包。
米哈尔Zabielski

7
@MichałZabielski:如果在定义它之前声明它,则可以递归调用它。
P爸爸

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啊,我在读问题时错过了“声明”一词。我的错。
尼克,

1
@corazza另外,我也弄糟了语法。需要删除函数名称。因此,它基本上是一个立即调用的lambda函数。
尼克,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.