♻️

Goでクロージャを使ってジェネレータっぽいことをする

2023/09/09に公開

背景

  • 長い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