Go言語の無名関数って、なんだか特別な響きがありますよね!
プログラミングをしていると、時々 `func()` みたいな、名前がついていない関数を見かけることがあると思います。「え、名前なくて大丈夫なの?」「どんな時に使うの?」そんな疑問を持っているかもしれませんね。
実は、無名関数はGo言語を使いこなす上で、とっても便利な機能なんです。コードがスッキリしたり、一時的な処理をサッと書けたり、Goの得意技である並行処理(ゴルーチン)と組み合わせたり…使いこなせると、あなたのGoプログラミングが一段レベルアップすること間違いなし!
この記事では、そんなGo言語の無名関数について、ゼロから丁寧に解説していきます。この記事を読むと、以下の点がバッチリわかりますよ。
- 無名関数がそもそも何なのか
- 無名関数の基本的な書き方
- 引数や戻り値がある場合の書き方
- 定義してすぐ実行する方法(IIFE)
- ゴルーチンやdeferでの実践的な使い方
- コールバック関数としての使い方
- 使うときにちょっと気をつけたいこと(クロージャ)
Go言語の無名関数とは?
さて、まずは「無名関数」が一体何者なのか、その正体を明らかにしましょう。
無名関数は、その名の通り「名前を持たない関数」のことです。関数リテラル(Function Literal)と呼ばれることもあります。普段、関数を作るときは `func add(x, y int) int { ... }` みたいに名前をつけますよね。無名関数は、`add` のような名前を省略して定義する関数なんです。
「名前がないのにどうやって使うの?」と思いますよね。大丈夫、ちゃんと使う方法があります。主な使い方は以下の2パターンです。
- 変数に関数を代入する
- 関数を定義して、その場ですぐに実行する
なぜ無名関数なんてものがあるのでしょうか? それは、関数をまるでデータ(数値や文字列とか)と同じように扱えるようにするためです。
関数を変数に入れたり、他の関数に引数として渡したり、戻り値として返したり…。無名関数を使うと、プログラムの表現力が豊かになり、コードがより柔軟になるというメリットがあります。
例えば、ちょっとした処理をわざわざ名前付き関数として定義するまでもない場合や、特定の場所だけで使いたい処理がある場合に、サッと無名関数で書けるとコードがスッキリしますね。
Go言語における無名関数の基本的な書き方
では、実際に無名関数をどうやって書くのか見ていきましょう。基本形はとてもシンプルですよ。
func(引数リスト) 戻り値の型 { // 関数の処理内容 }
通常の関数定義から `func` の後ろの関数名をなくした形ですね。簡単でしょう?
一番シンプルな、引数も戻り値もない無名関数を変数に代入してみます。
package main import "fmt" func main() { // 無名関数を変数 `greet` に代入 greet := func() { fmt.Println("Hello, 無名関数!") } // 変数を使って関数を呼び出す greet() }
これを実行すると、こうなります。
Hello, 無名関数!
ね、変数 `greet` に関数そのものが代入されていて、普通の関数みたいに `greet()` で呼び出せていますよね。関数がまるで値のように扱えているのがポイントです。
引数と戻り値を持つ無名関数
もちろん、無名関数も引数を受け取ったり、戻り値を返したりできます。
例えば、二つの整数を受け取って、その合計を返す無名関数を作ってみましょう。
package main import "fmt" func main() { // 引数を2つ受け取り、合計(int)を返す無名関数を変数 `add` に代入 add := func(a int, b int) int { return a + b } // 関数を呼び出して結果を得る result := add(10, 20) fmt.Printf("計算結果: %d\n", result) // %d は整数を表示する書式指定子 // 別の値で試す result = add(5, 3) fmt.Printf("計算結果: %d\n", result) }
実行結果はこうです。
計算結果: 30 計算結果: 8
ちゃんと引数を渡せて、戻り値を受け取れていますね。書き方は通常の関数とほとんど同じなので、戸惑うことは少ないはずです。
無名関数の即時実行 (Immediately Invoked Function Expression - IIFE)
無名関数には、定義したその場ですぐに実行するという、ちょっと面白い使い方もあります。これを「即時実行関数」や「IIFE(イーフィー、またはアイアイエフイー)」と呼びます。
書き方は、無名関数を定義した直後に `()` をつけるだけ。
package main import "fmt" func main() { // 無名関数を定義して、その場で即時実行 func() { message := "いきなり実行!" fmt.Println(message) }() // ← ここで () をつけて実行! // IIFEの外からは message 変数にはアクセスできない // fmt.Println(message) // これはコンパイルエラーになる }
実行結果
いきなり実行!
この書き方のメリットは何でしょうか? 一つは、一時的な変数などを関数の中に閉じ込めて、外に影響を与えないようにできることです。
上の例だと `message` という変数は、即時実行された無名関数の中だけで有効です。関数の外からアクセスしようとするとエラーになります。ちょっとした初期化処理や、使い捨ての処理を書きたいときに便利ですね。
Go言語での無名関数の実践的な使い方
さて、基本的な書き方がわかったところで、ここからは無名関数がGo言語のどんな場面で活躍するのか、より実践的な使い方を見ていきましょう!
「なるほど、こんな時に使うのか!」と思えるはずです。
ゴルーチン (goroutine) での利用
Go言語といえば、並行処理を手軽に実現できる「ゴルーチン」が有名ですよね。このゴルーチンを起動するときに、無名関数がめちゃくちゃよく使われます。
やり方は簡単。`go` キーワードの後ろに、実行したい処理を書いた無名関数を続けるだけです。
package main import ( "fmt" "time" ) func main() { fmt.Println("メインの処理を開始します") // ゴルーチンで無名関数を実行 go func() { fmt.Println("ゴルーチンが動いています! (1)") time.Sleep(1 * time.Second) // 1秒待つ fmt.Println("ゴルーチン (1) 終了") }() // もう一つゴルーチンを起動 go func(message string) { fmt.Println("ゴルーチンが動いています!", message) time.Sleep(2 * time.Second) // 2秒待つ fmt.Println("ゴルーチン", message, "終了") }("(2)") // 引数を渡すことも可能 fmt.Println("メインの処理がゴルーチンを起動しました") // ゴルーチンの処理が終わるのを少し待つ (本来は sync.WaitGroup などを使う) time.Sleep(3 * time.Second) fmt.Println("メインの処理を終了します") }
実行結果(順序は実行毎に変わる可能性あり)
メインの処理を開始します メインの処理がゴルーチンを起動しました ゴルーチンが動いています! (1) ゴルーチンが動いています! (2) ゴルーチン (1) 終了 ゴルーチン (2) 終了 メインの処理を終了します
`go func() { ... }()` の形で、サッと並行処理を書き出せるのが、無名関数の大きな利点の一つです。わざわざ名前付きの関数を定義しなくても、その場で必要な処理をゴルーチンとして起動できる手軽さが良いですね。
`defer` 文での利用
`defer` は、関数が終了する(return する)直前に実行したい処理を登録しておくための仕組みです。ファイルやネットワーク接続のクローズ処理など、後片付けによく使われますね。
この `defer` と無名関数を組み合わせることも多いです。
package main import "fmt" func main() { fmt.Println("処理を開始します。") // defer で無名関数を登録 defer func() { fmt.Println("このメッセージは関数の最後に表示されます。") }() // 即時実行ではないことに注意! defer に関数を渡している fmt.Println("何かの処理を実行中...") value := 100 defer func(v int) { // defer登録時の v の値 (100) が使われる fmt.Printf("defer実行時の値: %d\n", v) }(value) // deferに登録する関数に引数を渡すこともできる value = 200 // 値を変更 fmt.Println("さらに処理を実行中...") fmt.Println("関数 main の終わりが近づいています。") // ここで defer された処理が(登録と逆順で)実行される }
実行結果
処理を開始します。 何かの処理を実行中... さらに処理を実行中... 関数 main の終わりが近づいています。 defer実行時の値: 100 このメッセージは関数の最後に表示されます。
無名関数を使うことで、`defer` で実行したい一連の処理をその場で定義できるため、コードの流れが分かりやすくなることがあります。
特に、`defer` を登録した時点での変数の値を後片付け処理で使いたい場合に、無名関数の引数として渡しておくテクニックは覚えておくと便利です。(上の例の `value` のように)
コールバック関数としての利用
「コールバック関数」というのは、他の関数に引数として渡される関数のことです。
ある処理が終わった後や、特定のイベントが発生した時に呼び出してほしい(コールバックしてほしい)処理を、関数として渡すイメージです。
Go言語では関数も値として扱えるので、無名関数をコールバックとして渡すのは非常に一般的です。
package main import "fmt" // int を受け取り bool を返す関数型を定義 (なくても良いが分かりやすさのため) type intPredicate func(n int) bool // スライスと判定関数を受け取り、条件を満たす要素だけを出力する関数 func filterAndPrint(numbers []int, check intPredicate) { fmt.Println("--- フィルタリング開始 ---") for _, num := range numbers { // 引数で受け取った判定関数 `check` を実行 if check(num) { fmt.Println(num) } } fmt.Println("--- フィルタリング終了 ---") } func main() { nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // 偶数かどうかを判定する無名関数をコールバックとして渡す filterAndPrint(nums, func(n int) bool { return n%2 == 0 }) // 5より大きいかどうかを判定する無名関数をコールバックとして渡す filterAndPrint(nums, func(n int) bool { return n > 5 }) }
実行結果
--- フィルタリング開始 --- 2 4 6 8 10 --- フィルタリング終了 --- --- フィルタリング開始 --- 6 7 8 9 10 --- フィルタリング終了 ---
`filterAndPrint` 関数は、具体的な判定ロジックを知りません。ただ、引数で渡された `check` という関数(ここでは無名関数)を呼び出して、その結果が `true` なら出力する、という動作をします。
このように、処理の一部を呼び出し側が自由に差し替えられるようにするのがコールバックの強力な点であり、無名関数はそのための処理を簡潔に書くのに役立ちます。
Go言語の無名関数を使う上での注意点
とっても便利な無名関数ですが、一つだけ、特に初心者がハマりやすいポイントがあります。
それは「クロージャ」と「変数のキャプチャ」に関する挙動です。ここをしっかり押さえておけば、無名関数マスターにまた一歩近づけますよ!
クロージャと変数のキャプチャ
まず「クロージャ」という言葉、聞いたことがあるでしょうか? 難しく考えなくて大丈夫です。
これは、「関数が、それが定義された場所(環境)にある変数を覚えていて、後からでもアクセスできる仕組み」のことです。無名関数は、このクロージャの性質を持っています。
例えば、こんな感じです。
package main import "fmt" func main() { count := 0 // main 関数のスコープにある変数 // 無名関数を定義して変数 incrementer に代入 incrementer := func() { // 無名関数の中から外側の変数 `count` にアクセスしている count++ fmt.Printf("Count: %d\n", count) } incrementer() // 実行すると count が 1 増える incrementer() // もう一度実行するとさらに 1 増える fmt.Printf("最終的な Count: %d\n", count) }
実行結果
Count: 1 Count: 2 最終的な Count: 2
無名関数 `incrementer` は、自分の外側にある `count` 変数を「覚えて」いて、呼び出されるたびにその値を変更できていますよね。これがクロージャです。
ここまでは良いのですが、注意が必要なのは、特にループの中でゴルーチンを起動する時です。以下のコードを見てください。
package main import ( "fmt" "sync" // ゴルーチンの完了を待つために使用 ) func main() { var wg sync.WaitGroup // WaitGroup を作成 fmt.Println("--- NGな例 ---") for i := 0; i < 3; i++ { wg.Add(1) // ゴルーチンのカウンターを増やす go func() { defer wg.Done() // ゴルーチン完了時にカウンターを減らす // ループ変数 `i` を直接参照している fmt.Printf("NG: ループ変数 i の値: %d\n", i) }() } wg.Wait() // 全てのゴルーチンが完了するのを待つ fmt.Println("\n--- OKな例 ---") for i := 0; i < 3; i++ { wg.Add(1) // ループ変数を無名関数の引数として渡す go func(loopVar int) { defer wg.Done() fmt.Printf("OK: 引数で受け取った値: %d\n", loopVar) }(i) // ← ここでループ変数 `i` の現在の値を渡す } wg.Wait() }
実行結果(NG例の出力は実行タイミングで変わる可能性が高い):
--- NGな例 --- NG: ループ変数 i の値: 3 NG: ループ変数 i の値: 3 NG: ループ変数 i の値: 3 --- OKな例 --- OK: 引数で受け取った値: 1 OK: 引数で受け取った値: 0 OK: 引数で受け取った値: 2 (OK例の順序は変わることがありますが、0, 1, 2 が表示されます)
NGな例では、なぜか全部 `3` になってしまいましたね!
これは、ゴルーチンが実際に動き出す頃には、`for` ループが既に回りきって `i` の値が `3` になってしまっているからです。ゴルーチン内の無名関数は、ループ変数 `i` そのものを参照(キャプチャ)しているので、実行時の `i` の値を見てしまうのです。
OKな例では、`go func(loopVar int) { ... }(i)` のように、ループの各繰り返し時点での `i` の値を、無名関数の引数 `loopVar` として渡しています。
こうすることで、それぞれのゴルーチンは起動された瞬間の `i` の値(0, 1, 2)を `loopVar` として保持できるため、期待通りの結果になります。これは非常に良く使われるテクニックなので、しっかり覚えておきましょう!
【まとめ】Go言語の無名関数を使いこなそう
お疲れ様でした!Go言語の無名関数について、基本的なところから実践的な使い方、そしてちょっとした注意点まで見てきました。
ポイントを振り返ってみましょう。
- 無名関数は名前のない関数で、関数リテラルとも呼ばれる。
- 変数に代入したり、即時実行したりできる。
- `func(引数) 戻り値 { 処理 }` という形で書く。
- ゴルーチン (`go func()`) や `defer`、コールバック関数として大活躍する。
- クロージャの性質を持ち、外側の変数を参照できる。
- ループ内でゴルーチンを使うときは、ループ変数の値渡しに気をつける!
無名関数は、Go言語のコードをより簡潔に、そして柔軟にするための強力な機能です。
最初は少し戸惑うかもしれませんが、実際にコードを書いて動かしてみるのが一番の近道!ぜひ、あなたのGoプログラミングに無名関数を取り入れて、もっともっとGo言語を楽しんでくださいね!
【関連記事】 Go言語とは?特徴・できること・将来性
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。