こんにちは。Tomoyuki(@tomoyuki65)です。
Go言語(Golang)のバージョン1.23からイテレータ(range over func)が導入され、引数に「yield」のついた関数が「for range文」で扱えるようになりました。
この記事では、そんなイテレータ(range over func)について解説します。
Go言語(Golang)のイテレータ(range over func)について
Go言語(Golang)のバージョン1.23から、主に以下の3種類の関数が「for range文」で扱えます。
・func f(yield func(V) bool) {}
・func f(yield func(K, V) bool) {}
package main
import (
"fmt"
)
// パラメータが空のyield関数を引数に持つ関数
func NotYieldFunc(yield func() bool) {}
// パラメータが空のyield関数を引数に持つ関数で、yieldを1回実行する場合
func OneYieldFunc(yield func() bool) {
if !yield() {
return
}
}
func main() {
for range NotYieldFunc {
fmt.Println("NotYieldFuncでrangeのループ処理が実行されました!")
}
for range OneYieldFunc {
fmt.Println("OneYieldFuncでrangeのループ処理が実行されました!")
}
}
実行結果
OneYieldFuncでrangeのループ処理が実行されました!
次にパラメータ有りのyield関数を使った例としては次の通りで、yield(10)の部分で渡した値をmain処理のrangeの部分で受け取ってループ処理を行うようになってます。
package main
import (
"fmt"
)
// int型のパラメータ有りのyield関数を引数に持つ関数で、yieldを1回実行する場合
func IntYieldFunc(yield func(int) bool) {
if !yield(10) {
return
}
}
func main() {
for v := range IntYieldFunc {
fmt.Println("IntYieldFuncの値:", v)
}
}
実行結果
IntYieldFuncの値: 10
スライス(slice)のデータをループ処理させる例
実際にはスライス(slice)やマップ(map)のデータをループ処理で処理させたい場合に使ったりすると思いますが、例えばスライスのデータを処理する例は次の通りです。
package main
import (
"fmt"
)
// 偶数を2倍にする関数
func DoubleAnEvenNumber(nums []int) func(yield func(int) bool) {
return func(yield func(int) bool) {
for _, v := range nums {
if v%2 == 0 {
if !yield(v * 2) {
return
}
} else {
if !yield(v) {
return
}
}
}
}
}
func main() {
// 1〜10の数値を持つスライスを定義
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// スライスの値を偶数の値は2倍にして表示させる
for v := range DoubleAnEvenNumber(nums) {
fmt.Println(v)
}
}
実行結果
1
4
3
8
5
12
7
16
9
20
また、イテレータに関するパッケージとして「iter」が導入されていて、これにイテレータで使う型も定義されているため、iterパッケージを利用すると以下のようにも記述できます。
package main
import (
"fmt"
"iter"
)
// 偶数を2倍にする関数(戻り値の型にiterパッケージを利用)
func DoubleAnEvenNumber(nums []int) iter.Seq[int] {
return func(yield func(int) bool) {
for _, v := range nums {
if v%2 == 0 {
if !yield(v * 2) {
return
}
} else {
if !yield(v) {
return
}
}
}
}
}
func main() {
// 1〜10の数値を持つスライスを定義
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// スライスの値を偶数の値は2倍にして表示させる
for v := range DoubleAnEvenNumber(nums) {
fmt.Println(v)
}
}
実行結果
1
4
3
8
5
12
7
16
9
20
マップ(map)のデータをループ処理させる例
次にマップ(map)のデータを処理する例は次の通りです。
package main
import (
"fmt"
)
// 1000円以上のフルーツを100円割引する関数
func DiscountFruits(fruits map[string]int) func(yield func(string, int) bool) {
return func(yield func(string, int) bool) {
for k, v := range fruits {
if v >= 1000 {
if !yield(k, v-100) {
return
}
} else {
if !yield(k, v) {
return
}
}
}
}
}
func main() {
// マップ
fruits := map[string]int {
"Apple": 200,
"Muscat": 1000,
"Melon": 2000,
}
for k, v := range DiscountFruits(fruits) {
fmt.Printf("%s: %d 円\n", k, v)
}
}
実行結果
Apple: 200 円
Muscat: 900 円
Melon: 1900 円
また、iterパッケージを利用すると以下のようにも記述できます。
package main
import (
"fmt"
"iter"
)
// 1000円以上のフルーツを100円割引する関数(戻り値の型にiterパッケージを利用)
func DiscountFruits(fruits map[string]int) iter.Seq2[string, int] {
return func(yield func(string, int) bool) {
for k, v := range fruits {
if v >= 1000 {
if !yield(k, v-100) {
return
}
} else {
if !yield(k, v) {
return
}
}
}
}
}
func main() {
// マップ
fruits := map[string]int {
"Apple": 200,
"Muscat": 1000,
"Melon": 2000,
}
for k, v := range DiscountFruits(fruits) {
fmt.Printf("%s: %d 円\n", k, v)
}
}
実行結果
Apple: 200 円
Muscat: 900 円
Melon: 1900 円
最後に
今回はGo言語のバージョン1.23から導入されたイテレータ(range over func)について解説しました。
使い方などがちょっと難しいですが、今まであるデータをスライスやマップに変換などをしてからrangeのループ処理をさせていたようなところを、イテレータでループ処理をさせれられるというような感じだと思うので、大量データを扱う際に上手く使えるとメモリ消費量を抑えながらパフォーマンス改善に繋げられそうです。
コメント