Go練習問題3-05:クロージャを理解しよう

記事内に商品プロモーションを含む場合があります

Go言語の初心者向け問題3-05:クロージャを理解しよう

この問題を解くために必要な知識:
レベル1~2の知識関数の定義と呼び出し関数の戻り値と複数の戻り値関数の可変長引数無名関数(匿名関数)クロージャ構造体の定義とコンストラクタ関数構造体の埋め込みメソッドの定義とレシーバインターフェース型アサーション型スイッチ

<<前の問題 問題集Top 次の問題>>

Go言語の文法「クロージャ」とは





ここではクロージャの意味や使い方を復習します。必要ない方はここをクリックして練習問題へ飛びましょう。

Go言語では、関数を扱う上で非常に便利な概念の一つとして「クロージャ」があります。クロージャは、関数の外部にある変数へのアクセスを保持し続ける関数のことです。

もう少し具体的に言うと、クロージャは自分が定義されたスコープ内にあった変数を「捕捉」し、その変数を関数外で参照できるようにします。

クロージャの具体例

以下は、クロージャを使った簡単な例です。

func counter() func() int {
    i := 0
    return func() int {
        i++  // 外部の変数iを捕捉している
        return i
    }
}

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

このコードでは、counter 関数が新しい関数を返します。この返された関数は、counter 関数内で定義された変数 i を捕捉し、それを増加させて返します。

重要なポイントは、この icounter 関数が終了した後でも、その返された関数内でアクセス可能であることです。

クロージャが役立つ場面

クロージャは、以下のような場面で特に役立ちます。

  1. 状態を保持する関数を作成する: 例えば、カウンターのように、関数呼び出し間で状態を保持したい場合に役立ちます。
  2. コールバック関数やイベントハンドラでの利用: クロージャを使うことで、外部のコンテキストを保持しつつ、関数を柔軟に定義できます。
  3. 高階関数の作成: 関数を返す関数や、関数を引数に取る関数を作成する際に、クロージャは強力なツールとなります。

まとめ

Go言語のクロージャは、関数のスコープ外の変数への参照を保持できる強力な機能です。これにより、状態を保持する関数や、より柔軟なコード設計が可能になります。

初心者にとっては少し難しい概念かもしれませんが、理解して使いこなせるようになると、プログラムの設計の幅が広がります。

Go練習問題3-5:クロージャによる状態管理を学ぼう

Go言語には「クロージャ」と呼ばれる機能があり、外部の変数を保持して再利用することができます。

この問題では、クロージャを使用してカウンター機能を持つ関数を作成します。関数を呼び出すたびにカウントが1つずつ増加するようにしてください。

この問題の要件

以下の要件に従ってコードを完成させてください。

  1. カウンターを生成する関数の作成: createCounter 関数を作成してください。この関数は、整数カウンターの現在の値を返す無名関数(クロージャ)を返す必要があります。
  2. クロージャの実装: createCounter 関数内で宣言された整数型変数 i を利用します。この変数は、createCounter 関数が返す無名関数の中でインクリメントされ、その結果が関数の返り値となります。この変数 icreateCounter のスコープに閉じ込められ、外部から直接アクセスできません。
  3. カウンターの独立性: createCounter 関数を複数回呼び出して、それぞれが独立したカウンターを生成することを確認してください。具体的には、createCounter 関数を使って生成された複数のクロージャは、異なるカウンター変数を持ち、それぞれが個別にカウントされる必要があります。
  4. メイン関数の実装: main 関数では、createCounter 関数を使ってカウンターを生成し、生成したカウンターを複数回呼び出してカウント値を確認してください。また、別のカウンターを生成し、その独立性も確認します。

ただし、以下のような実行結果となるコードを書くこと。

*****↓↓正解コードの実行結果の例↓↓*****

カウント: 1
カウント: 2
カウント: 3
別のカウンタのカウント: 1

この問題を解くヒント

1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。

ヒント1【コードの構成を見る】

正解のコードは上から順に以下のような構成となっています。

1.main パッケージの宣言
2.fmt パッケージのインポート
3.createCounter 関数の定義
 3-1. カウンター変数 i の宣言と初期化
 3-2. クロージャを返す無名関数の定義
  3-2-1. カウンター変数 i のインクリメント
  3-2-2. インクリメントされた i の値を返す
4.main 関数の定義
 4-1. createCounter 関数の呼び出しによるカウンター生成
 4-2. counter 関数の呼び出しと結果の表示
 4-3. anotherCounter 関数の呼び出しと結果の表示

ヒント2【穴埋め問題にする】

以下のコードをコピーし、コメントに従ってコードを完成させて下さい。

package main

import "fmt"

// カウンターを生成するクロージャ関数
func createCounter() func() int {
    /*【穴埋め問題1】 カウンター変数 i を初期化するコードを書いてください */
    
    /*【穴埋め問題2】 カウンターをインクリメントして返す無名関数を返すコードを書いてください */
}

// メイン関数
func main() {
    // 新しいカウンターを作成
    /*【穴埋め問題3】 createCounter 関数を呼び出してカウンターを生成するコードを書いてください */
    
    // カウンターを何度か呼び出してみる
    fmt.Println("カウント:", counter()) // 1 が表示される
    fmt.Println("カウント:", counter()) // 2 が表示される
    fmt.Println("カウント:", counter()) // 3 が表示される

    // 別のカウンターを作成
    /*【穴埋め問題4】 別のカウンターを生成するために createCounter 関数を再度呼び出すコードを書いてください */
    
    fmt.Println("別のカウンタのカウント:", anotherCounter()) // 1 が表示される
}

 

このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。

解答例と解説

この問題の一つの正解例とそのコードの解説を以下に示します。

正解コードの例

例えば以下のようなプログラムが考えられます。

package main

import "fmt"

// カウンターを生成するクロージャ関数
// 外部の変数 'i' を捕捉し、その値を保持します
func createCounter() func() int {
    i := 0 // カウンター変数
    // クロージャ: カウンターをインクリメントしてその値を返す無名関数
    return func() int {
        i++        // 'i' をインクリメント
        return i   // 現在のカウント値を返す
    }
}

func main() {
    // 新しいカウンターを作成
    counter := createCounter()

    // カウンターを何度か呼び出してみる
    fmt.Println("カウント:", counter()) // 1 が表示される
    fmt.Println("カウント:", counter()) // 2 が表示される
    fmt.Println("カウント:", counter()) // 3 が表示される

    // 別のカウンターを作成
    anotherCounter := createCounter()
    fmt.Println("別のカウンタのカウント:", anotherCounter()) // 1 が表示される
}

正解コードの解説

このGoコードは、クロージャの概念を利用してカウンター機能を実現しています。

クロージャとは、関数が宣言されたスコープの変数をキャプチャし、それらの変数を保持する機能を持つ関数のことです。

以下に、コードの各部分について解説します。

パッケージ宣言とインポート

package main

import "fmt"

Goプログラムのエントリーポイントとなる main パッケージを宣言しています。また、標準出力を行うために fmt パッケージをインポートしています。

createCounter関数の定義

func createCounter() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

createCounter 関数は、カウンターを生成する関数です。この関数は、整数型変数 i を初期化し、それをインクリメントする無名関数(クロージャ)を返します。

このクロージャは、createCounter 関数のスコープにある i をキャプチャし、関数が呼ばれるたびにその値を更新します。

main関数の定義

func main() {
    counter := createCounter()

    fmt.Println("カウント:", counter()) // 1 が表示される
    fmt.Println("カウント:", counter()) // 2 が表示される
    fmt.Println("カウント:", counter()) // 3 が表示される

    anotherCounter := createCounter()
    fmt.Println("別のカウンタのカウント:", anotherCounter()) // 1 が表示される
}

main 関数は、プログラムの開始点です。最初に createCounter 関数を呼び出してカウンターを生成し、生成したカウンターを何度か呼び出して結果を表示します。

また、別のカウンターを作成して、その独立性も確認しています。このように、クロージャはカウンターごとに異なる状態を保持することができます。

クロージャの解説

このコードの核心は、クロージャの使用です。クロージャは、関数が定義されたスコープの変数をキャプチャし、その状態を保持し続けます。具体的には、createCounter が返す無名関数は、i という変数をキャプチャし、その値を保持し続けます。

そのため、同じカウンターが呼び出されるたびに、i の値が1ずつ増加します。一方、別のカウンターを作成すると、そのカウンターは新しい i を持ち、独立した状態を保持します。

このように、クロージャを使用することで、関数が呼び出されるたびに異なる状態を管理できるようになります。これは、Goにおける強力なプログラミング手法の一つです。

<<前の問題 問題集Top 次の問題>>

この問題への質問・コメント

この問題を作成するにあたりAIを活用しています。

問題ないことは確認していますが、もし間違いや表現の違和感などありましたら、ご指摘頂けると大変助かります。






    Go練習問題集へ戻る
    トップページへ戻る