Goでネストされた関数宣言を許可しないことで軽減される問題は何ですか?


87

ラムダは期待どおりに機能します:

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。でもそれもラムダですよね?私は...あなたのコメントを得ることはありません
corazza

構文の2番目の例を有効にすると、最初のケースではサポートされない機能は何ですか?
not_a_Golfer 2014

@yannbaneこれはラムダ式です。JSのような別の関数内で関数を宣言することはできないと思います。したがって、ラムダを使用するのが最適だと思います。
ymg 2014

@Not_a_Golfer:1つの可能性は、JavaScriptのように実装することです。基本的に、変数への関数の割り当ては、制御フローがそのような変数に影響を与えるのに対し、JavaScriptの関数は影響を受けないため、関数の宣言とは大きく異なります。つまりinc()、実際の宣言の前に2番目の例を呼び出すことができます。だが!私は理由を探しています。Goについてはよくわかりませんが、このルールの背後にあるロジックが何であったかを知りたいと思います。
corazza 2014

回答:


54

この明らかな機能が許可されない理由は3つあると思います

  1. コンパイラが少し複雑になります。現時点では、コンパイラはすべての関数がトップレベルにあることを認識しています。
  2. これにより、新しいクラスのプログラマーエラーが発生します。何かをリファクタリングして、誤って一部の関数をネストする可能性があります。
  3. 関数とクロージャの構文が異なるのは良いことです。クロージャを作成することは、関数を作成するよりも潜在的にコストがかかるため、それを実行していることを知っておく必要があります。

これらは私の意見ですが、言語設計者からの公式の発表は見ていません。


2
Pascal(少なくともDelphiの化身)はそれらを正しくシンプルにしました。ネストされた関数は通常のように動作しますが、それを囲む関数のスコープ内の変数にもアクセスできます。これらを実装するのは難しいとは思いません。一方、Delphiコードをたくさん書いたので、入れ子関数がひどく必要かどうかはわかりません。ときどき気の利いた感じがしますが、囲んでいる関数を吹き飛ばして読みにくくする傾向があります。また、親の引数にアクセスすると、これらの変数が暗黙的にアクセスされるため(仮パラメーターとして渡されないため)、プログラムが読みにくくなる可能性があります。
kostix 2014

1
ローカル関数は、メソッドを抽出する途中の中間リファクタリングステップとして最適です。C#では、囲んでいる関数から変数をキャプチャすることを許可されていない静的ローカル関数を導入すると、これらの価値が高まり、パラメータとして何かを渡す必要があります。静的な局所関数は、ポイント3を問題にしません。ポイント2も私の観点からは問題ではありません。
CosminSontu19年

47

確かにそうです。それらを変数に割り当てる必要があります。

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

5
注目に値するのは、そのような関数(inc)を再帰的に呼び出すことはできないということです。
モーシンケール

1
再帰的に呼び出すには、前方宣言する必要がありますvar inc func(int) int
Izana 2010

28

よくある質問(FAQ)

Goに機能Xがないのはなぜですか?

すべての言語には斬新な機能が含まれており、誰かのお気に入りの機能が省略されています。Goは、プログラミングの効率性、コンパイルの速度、概念の直交性、および並行性やガベージコレクションなどの機能をサポートする必要性を考慮して設計されました。お気に入りの機能が合わない、コンパイル速度やデザインの明瞭さに影響する、または基本的なシステムモデルが難しくなりすぎるために、お気に入りの機能が欠落している可能性があります。

Goに機能Xがないことが気になる場合は、ご容赦ください。Goにある機能を調査してください。Xの不足を興味深い方法で補うことがわかるかもしれません。

入れ子関数を追加することの複雑さと費用を正当化するものは何ですか?入れ子関数なしではできないことをyauは何をしたいですか?など。


19
公平を期すために、入れ子関数を許可することによって引き起こされる特定の複雑さを誰かが示したとは思いません。さらに、引用された哲学には同意しますが、入れ子関数を「機能」と呼ぶのが合理的かどうかはわかりません。入れ子関数を許可することで可能になる複雑さを知っていますか?それらはラムダの構文糖衣であると思います(他の合理的な動作は考えられません)。
joshlf 2014

goがコンパイルされているため、このAFAIKを実行する唯一の方法は、ラムダを定義するための別の構文を作成します。そして、そのユースケースは実際には見当たりません。リアルタイムで作成された静的関数内に静的関数を含めることはできません-関数を定義する特定のコードパスを入力しないとどうなりますか?
not_a_Golfer 2014

ラムダインターフェース{}を渡してassertと入力するだけです。例えば。f_lambda:= lambda(func()rval {})またはプロトタイプが何であれ。func decl構文はこれをサポートしていませんが、言語は完全にサポートしています。
BadZen 2015


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

内部関数は、多くの場合、ローカルのゴルーチンに便利です。

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

goでのクロージャは、単純な機能とはいくつかの点で異なります。たとえば、クロージャを再帰的に呼び出すことはできません。
のMichałZabielski

7
@MichałZabielski:定義する前に宣言すれば、再帰的に呼び出すことができます。
Pパパ

1

()最後に追加して、すぐに呼び出す必要があります。

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

編集:関数名を持つことはできません...したがって、すぐに呼び出されるのはラムダ関数です:

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

1
関数定義に期待するものと一致しないうーん
corazza 2018

1
@corazzaああ、質問を読んだときに「宣言」という言葉を見逃しました。私の悪い。
ニック

1
@corazzaまた、構文も台無しにしました。関数名を削除する必要があります。つまり、基本的にはすぐに呼び出されるラムダ関数です。
ニック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.