Go言語のクロージャーを初心者向けにゆっくりと解説してみる

Go言語
この記事は約7分で読めます。
Go初学者
Go初学者

クロージャーが理解できなんです…ゆっくりと説明してほしいな…

この記事ではクロージャーについて理解できます!
  • Go言語のクロージャーの動きをコードで実装できる

Go言語のクロージャーについてゆっくりと解説していきます。

もりぴ
もりぴ

私自身、クロージャーを理解するまでは本当に苦労しましたね。

この記事では、できるだけ難しい言葉を使わずにプログラムで動きを確認しながら解説していきます。

もりぴ
この記事を書いた人

XHTML1.0時代にHTML&CSSを勉強した経験あり。無趣味だった私が2020年5月からプログラミング学習を開始し現在も挫折せずに趣味で学習を楽しんでいる51歳。プログラミングの楽しさをブログを通してお伝えしていきます。

もりぴをフォローする

Go言語のクロージャーを解説する前に

まずは、クロージャーの意味を引用します。

クロージャ(クロージャー、英語: closure)、関数閉包はプログラミング言語における関数オブジェクトの一種。いくつかの言語ではラムダ式や無名関数にて利用可能な機能・概念である。引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。関数とそれを評価する環境のペアであるともいえる。

引用元:ウキペディア
もりぴ
もりぴ

私はこの日本語を読んで理解することができませんでした…

それで、私はクロージャーの概念を知ることをあきらめて…

クロージャーとはどういう動きをするプログラムなのか?

という発想に切りかえて、プログラムで動きを確認し理解してきました。

私がクロージャーを理解できた順番通りに、プログラムをご紹介いたします。

話が周りくどくなるかもしれませんが、お付き合いください。

Go言語のクロージャーをゆっくり解説

それでは、Go言語のクロージャー解説を開始します。

事前知識として、関数の基礎と無名関数を理解しておく必要があります。

当ブログ記事を参考にしてみてください。

クロージャーを使う前のプログラム〜その1〜

まずは、下記のプログラムを確認してください。

package main

import "fmt"

func main() {
    x := 0
    // 変数に無名関数を代入
    add := func() int {
        x++
        return x
    }
    fmt.Println(add())
    fmt.Println(add())
    fmt.Println(add())
}
// 出力結果
$ go run main.go
1
2
3

このプログラムは関数が実行されるたびに、変数x1 増加するプログラムです。

このプログラムのポイントは、関数が実行されても 変数x の値は初期化されずに変わらない。

もりぴ
もりぴ

ということです。

でも、このプログラムに問題が潜んでいて、変数x の値は簡単に変えれるのです。

それではまずいので、main関数の外に出して関数化してみましょう。

クロージャーを使う前のプログラム〜その2〜

では、先程のプログラムをmain関数の外で関数化してみます。

package main

import "fmt"

func addGen() int {
    x := 0
    x++
    return x
}

func main() {
    // 関数を実行する 
    fmt.Println(addGen())
    fmt.Println(addGen())
    fmt.Println(addGen())
}
// 出力結果
$ go run main.go
1
1
1

プログラムの出力結果を見てわかる通り、見事に変数が初期化されています。

これだと、関数が呼ばれるたびに変数が初期化されてしまします。

ここで登場するのがクロージャーです。

クロージャーを実装する

ここでクロージャーを実装します。

ポイントとしては、関数の戻り値に関数を指定することと戻り値の関数は無名関数であるということです。

もりぴ
もりぴ

わかりずらいですよね…実際にプログラムを確認してみましょう。

package main

import "fmt"

// クロージャーの実装
func addGen2() (func() int) {
    x := 0
    return func() int {
        x++
        return x
    }
}

func main() {
    // addGen2は関数が戻り値なので変数に代入する
    add2 := addGen2()
    fmt.Println(add2())
    fmt.Println(add2())
    fmt.Println(add2())
}
// 出力結果
$ go run main.go
1
2
3

出力結果が最初と同じになりました!

ここでのポイント!
  • 関数の戻り値が関数であること (func() int) で戻り値を指定
  • 戻り値の関数を無名関数で作成
  • 関数の戻り値が関数なので、変数に代入して使用すること
  • 関数を実行するには、関数を代入した変数で実行すること

さらにポイントとなるのが、 add2 := addGen2() の部分です。

変数add2 の中の関数はどうなっているのでしょうか?

// 元の関数
func addGen2() (func() int) {
    x := 0
    return func() int {
        x++
        return x
    }
}

// 変数add2の中の関数の正体!
// ココ重要!!
func() int {
    x++
    return x
}

変数add2 の関数は上記のコードを確認してみると、fmt.Println(add2()) が実行されても x := 0 の処理が無いので 変数x は初期化されることないのです。

なので、add2 := addGen2() が参照され続けている限り初期化処理がされないのです。

では、もう一度初期化して計算し直す場合は変数を新たに宣言すれば大丈夫です。

それも、プログラムで確認していきましょう。

package main

import "fmt"

// クロージャーの実装
func addGen2() (func() int) {
    x := 0
    return func() int {
        x++
        return x
    }
}

func main() {
    // addGen2は関数が戻り値なので変数に代入する
    add2 := addGen2()
    fmt.Println(add2())
    fmt.Println(add2())
    fmt.Println(add2())

    // 新たに変数add3を宣言してaddGen2の戻り値を代入
    add3 := addGen2()
    fmt.Println(add3())
    fmt.Println(add3())
    fmt.Println(add3())
}
// 出力結果
$ go run main.go
1
2
3
1
2
3
もりぴ
もりぴ

このプログラムこそ、クロージャーの一番簡易的なプログラムの動きなのです。

【まとめ】Go言語のクロージャーについて

私がいろんなブログ記事や動画教材を見て、やっと辿り着いたクロージャーについて理解です。

クロージャーの概念を知ろうとすると、プログラミング初心者は間違いなく挫折します。

それは、普段聞き慣れない単語が多く単語の意味を調べても意味がわからないという、理解できない無限ループにハマるからです。

だから、クロージャーの概念ではなくプログラムの動きからクロージャーを理解していきましょう。

Go言語のクロージャーで押さえておくこと!
  • 関数が参照され続けている限り、変数を初期化せずに値を保持しておきたい時にクロージャーを使う!

以上です。

クロージャーはプログラミングでも難しい部類のひとつだと思います。

わからなくても大丈夫です。

わからなければ次に進んでGo言語に多くふれていき、またクロージャーを学習すれば理解できることでしょう。

諦めずに頑張っていきましょうね!

comment

タイトルとURLをコピーしました