メインコンテンツにスキップ
モジュール 3 · レッスン 4 / 5コース内の 14/50~12 min
モジュールのレッスン (4/5)

値としてのクロージャと関数

Go では、関数は 第一級の値 です。関数は次の値に割り当てることができます。 変数を他の関数に渡して返します。 クロージャとは、 の変数を キャプチャする関数リテラル (匿名) それが定義される環境。

関数リテラル

インラインで記述された名前のない関数。すぐに電話をかけるか、 それを割り当てます:

Go
greet := func(name string) {
    fmt.Println("ciao", name)
}
greet("Anna")

// invocata immediatamente:
func() { fmt.Println("subito") }()

greet のタイプは func(string) です。

環境を捉える: クロージャ

関数リテラルは、以下に表示される変数を読み取り、変更できます。 外部関数が戻った後でも、その字句スコープは次のようになります。

Go
func counter() func() int {
    n := 0
    return func() int {
        n++
        return n
    }
}

c := counter()
fmt.Println(c(), c(), c())  // 1 2 3

n は、少なくとも 1 つのクロージャがそれを参照している限り「存続」します。それぞれの電話に counter() は、新しい独立した n を作成します。

コールバックとしてのクロージャ

非常に一般的なパターン:

Go
nums := []int{3, 1, 2}
sort.Slice(nums, func(i, j int) bool {
    return nums[i] < nums[j]
})

クロージャは nums をキャプチャし、順序を定義します。

パラメータとしての関数

関数をパラメータとして受け入れるには、その型を書き出します。

Go
func apply(nums []int, f func(int) int) []int {
    out := make([]int, len(nums))
    for i, n := range nums {
        out[i] = f(n)
    }
    return out
}

double := func(x int) int { return x * 2 }
apply([]int{1, 2, 3}, double)   // [2 4 6]

署名が読みにくくなった場合は、タイプ エイリアスを定義します。

Go
type Transform func(int) int
func apply(nums []int, f Transform) []int { ... }

試してみてください

運動#go.m3.l4.e1
試行回数: 0読み込み中…

クロージャを返す counter() を定義します。クロージャを呼び出すたびにインクリメントされ、内部カウンタが返されます。

エディターを読み込み中…
ヒントを表示

`n` はリターンの前に宣言する必要があります。クロージャがそれをキャプチャします。

3 回の試行後に解決策が利用可能になります

運動#go.m3.l4.e2
試行回数: 0読み込み中…

「ciao」を出力する関数リテラルを f に代入し、それを呼び出します。

エディターを読み込み中…
ヒントを表示

構文: `f := func() { ... }`、次に `f()`。

3 回の試行後に解決策が利用可能になります

Quiz#go.m3.l4.e3
準備完了

これは何を印刷しますか? (クロージャスコープに注意してください)

Go
mk := func() func() int {
    x := 0
    return func() int { x++; return x }
}
a := mk()
b := mk()
fmt.Println(a(), a(), b())
回答の選択肢

要約

  • 関数は最上級のものです: 割り当て可能、渡せる、返却可能。
  • 関数リテラル: func(params) ret { ... }、匿名。
  • クロージャ: 字句スコープの変数をキャプチャし、それらを生きたままにします。
  • パラメーターの明示的な関数タイプ: func(int) int。
  • Go 1.22 以降、すべての for 反復にはループ変数の独自のコピーがあります。