♻️
Goでクロージャを使ってジェネレータっぽいことをする
背景
- 長いforブロックを記述しないため、Goで複数要素を持つデータの取得、加工、出力を独立した関数に分離して書く方法が知りたかった
- yieldが使えないみたいなのでGo的に書く方法を探していた
- クロージャ関数をyieldの代わりとして使えるみたいなので試してみた
サンプルコード
main.go
package main
import (
"errors"
"fmt"
)
// get_int_generator はint型の値を返すジェネレータを返す関数
func get_int_generator() func() (int, error) {
// テスト用のint型配列を定義する
myarray := [...]int{10, 20, 30}
// インデックスの添字を初期化
index := -1
// int型またはerror型を返す無名ジェネレータ関数を返却する
// index変数の状態を保持するクロージャ関数でもある
return func() (int, error) {
// 次のインデックスの添字を取得する
index++
// インデックスが配列を全て参照した場合はエラーを返して終了
if index >= len(myarray) {
return 0, errors.New("配列に値が存在しません")
}
// 配列内の指定したインデックスの値を返す
return myarray[index], nil
}
}
func main() {
// 状態を保持するクロージャ関数を取得する
generator := get_int_generator()
// ジェネレータの中身を2倍する関数を定義する
// このブロックでPythonの twiced_generator = map(lambda x: x * 2, generator) 的なことができる
// これだと最後に余計な 0 * 2 が実行されるがシンプルに加工処理を書ける
twiced_generator := func() (int, error) {
value, err := generator()
return value * 2, err
}
fmt.Println("ループ開始")
// for value in twiced_generator() みたいなことをするための記述
for {
// ループを抜けるまでジェネレータ関数が実行され新たな値が取得される
value, err := twiced_generator()
// エラーの場合ループを抜ける
if err != nil {
break
}
// 返却値があれば表示する
fmt.Println(value)
}
fmt.Println("ループ終了")
}
実行結果は以下
$ go run main.go
ループ開始
20
40
60
ループ終了
データの取得、加工、出力をそれぞれ別のブロックに収めることができた
Discussion