web-dev-qa-db-ja.com

Golangのクロージャーボディの後に「()」を追加するのはなぜですか?

私が読んでいる The Go Programming Language Specificationsそして、クロージャー本体の後に「()」が含まれていると、本当に理解できないことがわかりました。

Function literals

func(ch chan int){ch <-ACK} (replyChan) `

Defer statementsの例:

// f returns 1
func f() (result int) {
    defer func() {
        result++
    }() // why and how?
    return 0
}

クロージャー本体の後に「()」を追加および使用する理由については明確ではありませんが、誰かがこれを明確に説明できることを願っています。

42
Reck Hou

deferの-​​closureの後に(のみ)()を追加する必要があるわけではありません。 defer statement の言語仕様では、その 'Expression' alwaysは関数呼び出しでなければなりません。

そして、なぜそうなのですか? 'defer'であるかどうかにかかわらず、他の関数と同じです。

考慮してください:

func f() int { return 42 }

そして

a := f

b := f()

最初の式RHSは関数値です。 2番目のバージョンでは、RHSは値関数によって返される-つまり関数呼び出しです。

次のセマンティクスも同様です。

defer f

defer f()

ただし、最初のバージョンは 'defer'のコンテキストでは意味をなさないため、仕様では2番目の形式(のみ)でなければなりません。

上記で説明した「defer」ステートメント以外の関数呼び出しと直交しているため、IMHOを学ぶのも簡単です。

また、関数呼び出しはfn-exprの後に()が続くだけでなく、式リストは通常​​、括弧内にあることに注意してください(空のリストを含む)。次の間に大きな違いがあります。

for i := range whatever {
        defer func() { fmt. Println(i) }()
}

そして

for i := range whatever {
        defer func(n int) { fmt. Println(n) }(i)
}

最初のバージョンは、クロージャーexecutesが実行された瞬間に 'i'の値を出力し、2番目はdeferステートメントwasが実行された瞬間に 'i'の値を出力します。

64
zzzz

参照資料

Goプログラミング言語仕様

関数の種類

関数タイプは、同じパラメーターと結果タイプを持つすべての関数のセットを示します。

FunctionType   = "func" Signature .
Signature      = Parameters [ Result ] .
Result         = Parameters | Type .
Parameters     = "(" [ ParameterList [ "," ] ] ")" .
ParameterList  = ParameterDecl { "," ParameterDecl } .
ParameterDecl  = [ IdentifierList ] [ "..." ] Type .

関数宣言

関数宣言は、識別子、関数名を関数にバインドします。

FunctionDecl = "func" FunctionName Signature [ Body ] .
FunctionName = identifier .
Body         = Block .

関数リテラル

関数リテラルは、匿名関数を表します。これは、関数タイプと関数本体の仕様で構成されています。

FunctionLit = FunctionType Body .

関数リテラルはクロージャーです。周囲の関数で定義された変数を参照する場合があります。これらの変数は、周囲の関数と関数リテラルの間で共有され、アクセス可能な限り存続します。

関数リテラルは、変数に割り当てるか、直接呼び出すことができます。

通話

関数タイプfの式Fが与えられると、

f(a1, a2, … an)

引数a1, a2, … anfを呼び出します。

関数呼び出しでは、関数の値と引数は通常の順序で評価されます。それらが評価された後、呼び出しのパラメーターは値によって関数に渡され、呼び出された関数は実行を開始します。関数の戻りパラメーターは、関数が戻るときに値によって呼び出し元の関数に返されます。

ステートメントの延期

"defer"ステートメントは、周囲の関数が戻る時点まで実行が延期される関数を呼び出します。

DeferStmt = "defer" Expression .

式は関数またはメソッド呼び出しでなければなりません。 「defer」ステートメントが実行されるたびに、関数値と呼び出しへのパラメーターは通常どおり評価され、新しく保存されますが、実際の関数は呼び出されません。代わりに、遅延呼び出しは、周囲の関数が戻る直前、戻り値がある場合は評価された後、呼び出し元に返される前にLIFOの順序で実行されます。

あなたはまだ混乱しているので、あなたの質問に対する答えを提供する別の試みがあります。

質問のコンテキストでは、()は関数呼び出し演算子です。

たとえば、関数リテラル

func(i int) int { return 42 * i }

無名関数を表します。

()関数呼び出し演算子が続く関数リテラル

func(i int) int { return 42 * i }(7)

直接呼び出される匿名関数を表します。

通常、関数呼び出しでは、関数の値と引数は通常の順序で評価されます。それらが評価された後、呼び出しのパラメーターは値によって関数に渡され、呼び出された関数は実行を開始します。関数の戻りパラメーターは、関数が戻るときに値によって呼び出し元の関数に返されます。

ただし、deferステートメントを介して関数を呼び出すことは特別な場合です。 「defer」ステートメントが実行されるたびに、呼び出しに対する関数値とパラメーターが通常どおり評価され、新しく保存されますが、実際の関数は呼び出されません。代わりに、遅延呼び出しは、周囲の関数が戻る直前、戻り値がある場合は評価された後、呼び出し元に返される前にLIFOの順序で実行されます。

Deferステートメント式は、直接呼び出されない関数またはメソッドリテラルだけでなく、直接呼び出される関数またはメソッド呼び出しでなければなりません。したがって、関数またはメソッドリテラルの後に()関数呼び出し演算子を続けて、遅延ステートメント式が関数またはメソッド呼び出しになるようにする必要があります。

延期文

defer func(i int) int { return 42 * i }(7)

有効です。

延期文

defer func(i int) int { return 42 * i }

無効です:syntax error: argument to go/defer must be function call

16
peterSO

長い答えを読みたくない場合:

str := "Alice"
go func(name string) {
    fmt.Println("Your name is", name)
}(str)

と同じです:

str := "Alice"
f := func(name string) {
    fmt.Println("Your name is", name)
}
go f(str)
3
Erikas