PR

Go言語(Golang)のイテレータ(range over func)について

基礎

こんにちは。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() bool) {}
・func f(yield func(V) bool) {}
・func f(yield func(K, V) bool) {}
※VとKには任意の型を指定でき、rangeのループではその型の値をそれぞれ受け取ります。

 

まず具体的な使い方については以下の通りで、yieldの部分がコールバック関数になっており、main処理のrangeで指定した関数が1回実行され、その関数内で記述したyield()の回数分rangeのループ処理がされるようになっています。
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のループ処理をさせていたようなところを、イテレータでループ処理をさせれられるというような感じだと思うので、大量データを扱う際に上手く使えるとメモリ消費量を抑えながらパフォーマンス改善に繋げられそうです。

 

この記事を書いた人
Tomoyuki

SE→ブロガーを経て、現在はWeb系エンジニアをしています!

Tomoyukiをフォローする
基礎
スポンサーリンク
Tomoyukiをフォローする

コメント

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