はじめに
こんにちは、H×Hのセンリツ大好きエンジニアです。(同担OKです😉)
今回は、Go Conference 2024でも取り上げられていたrangefunc
という新たな機能について自分なりにまとめたので、そちらをご紹介します!
rangefuncとは?
公式リファレンス参照:https://go.dev/wiki/RangefuncExperiment
For a future Go release, the Go team is considering adding range-over function iterators. Go 1.22 contains a preliminary implementation of the change, enabled by setting GOEXPERIMENT=rangefunc when building your program. We invite anyone who wants to help us understand the effects of the change to try using GOEXPERIMENT=rangefunc and let us know about any problems or successes encountered.
将来的なイテレータの追加に伴って試験的に追加された機能です。
range
に関数が選択できるようになるという便利なものだそう。。。ほえ〜😮
(試験的な導入なので、利用する場合はGOEXPERIMENT=rangefunc
を実行時に含める必要があるとのこと)
Go 1.22で追加予定のrange over intと、GOEXPERIMENT入り予定のrange over funcを触ってみる
上記のeveryさんの記事によるとどうやら、現在のGoにはイテレータの標準がないために同じようなコードで実装出来ていないようです。
そのため、range
を拡張する形でGoのイテレータの標準化を行うという背景だそうですね〜。
確かに、Goは同じようなコードになる事による保守性が魅力の一つなので、書き方がバラバラになっちゃうと分かりづらくなりますもんね。
rangefuncを触ってみる
公式リファレンスにあるコードを少しいじってみました。
package main
import "fmt"
type Seq[V any] func(func(V) bool)
func Range[V any](s []V) Seq[V] {
return func(yield func(V) bool) {
for _, v := range s {
if !yield(v) {
return
}
}
}
}
func main() {
s := []string{"hello", "world", "!"}
for v := range Range(s) {
fmt.Println(v)
}
}
このコードを実行するには、GOEXPERIMENT=rangefunc
を設定します。
結果は以下のようになります。
$ GOEXPERIMENT=rangefunc go run main.go
hello
world
!
これ見ても何のこっちゃ🤔だと思うので、一つづつ解説していきます!
type Seq[V any] func(func(V) bool)
Seq[V]
というジェネリックな型を定義しています。
引数としては関数func(V) bool
を取る関数を表しています。
func(func(V) bool)
は、rangefunc
でイテレータになれる関数の型の一つになっています。
イテレータになれる関数の型は以下の通り
func(func(V) bool)
func(func() bool)
func(func(K, V) bool)
func Range[V any](s []V) Seq[V] {
return func(yield func(V) bool) {
for _, v := range s {
if !yield(v) {
return
}
}
}
}
Range[V]
はジェネリックな関数で、渡されたスライスの要素を順番に参照し、関数として返却する関数となっています。
引数のスライスの各要素に対してyield
関数を適用し、yield
がfalse
を返すとループを終了するようです。🥴
func main() {
s := []string{"hello", "world", "!"}
for v := range Range(s) {
fmt.Println(v)
}
}
main
関数では、文字列のスライスを作成し、Range
関数を呼び出しています。
Range(s)
が返す関数に対して、各要素を処理するための関数を渡し、fmt.Println(v)
で各要素を表示しています。
。。。なるほど、ざっくり理解しました。😇
Range
にあるyield
の役割は、反復を続けるかどうかを決めるものなので、break
文によってループを抜け出す場合などにfalse
を返却するとのこと。
なので、途中でbreakする処理を追加すると。。。
package main
import "fmt"
type Seq[V any] func(func(V) bool)
func Range[V any](s []V) Seq[V] {
return func(yield func(V) bool) {
for _, v := range s {
if !yield(v) {
return
}
}
}
}
func main() {
s := []string{"hello", "world", "!"}
for v := range Range(s) {
fmt.Println(v)
if v == "world" {
break
}
}
}
実行してみます。
$ GOEXPERIMENT=rangefunc go run main.go
hello
world
world
がRange
に渡された時にbreak
が走ることによって、yield
がfalse
になるためreturn
が返却されて処理が終わります。
まとめ
今回は個人的に気になっていたrangefunc
をざっくり理解するテーマでお送りしました。
実際、現場で開発する際にどのケースで使うかは把握できていませんが、以下の記事によると大きなスライスをチャンクに分けて処理する際に効果的っぽいですね。知りませんけど😇
Go1.22で登場するかも!?開発中の新機能イテレータを使ってみる!
最後までご覧いただきありがとうございました。
センリツでした🤓