iter.Seq と range-over-func
iter.Seq[E] は func(yield func(E) bool) という関数型で、Go 1.23 の range-over-func により for v := range s の形でそのまま回せる。イテレータを表す型そのものは 1.23 で標準に入った。
しかし iter.Seq[E] を繋いで加工する Map / Filter のようなアダプタは、go1.27rc3 時点でも標準ライブラリに無い。x/exp/xiter (#61898) がその一式を提案していたが 2025-05-21 に declined as retracted になっている (xiter は取り下げ済み)。標準に無い以上、自分でパッケージレベル関数として書くしかなく、Apply(Apply(words.Filter(isLong), toUpper), label) のように内側から外側へ読む形 (inside-out) になる。イテレータの利点である「繋いでも中間スライスができない」という性質も、この読みにくさに埋もれてしまう。
ここに ジェネリクスメソッド (Go 1.27) が効く。Stream[E] を iter.Seq[E] の defined type として定義し、Map をそのメソッドにすると、Map が返す型 F はレシーバのどこにも書かれていないため新しい型パラメータが要る。これが Go 1.26 まで書けなかった部分そのものである (メソッドはレシーバを持つ関数 の「レシーバ由来では決まらない型」の一例)。
type Stream[E any] iter.Seq[E]
func (s Stream[E]) Map[F any](f func(E) F) Stream[F] {
return func(yield func(F) bool) {
for v := range s {
if !yield(f(v)) {
return
}
}
}
}
使う側はメソッドチェーンで書ける。Java の Stream や Rust の Iterator に相当するものが自分で書けるようになる。
got := Stream[string](slices.Values(words)).
Filter(func(s string) bool { return len(s) > 2 }).
Map(strings.ToUpper).
Map(func(s string) int { return len(s) }).
Collect()
注意すべき点が2つある。ひとつは、新しい型パラメータが要るのは Map だけということ。Filter や Collect は戻り値の型がレシーバの E だけで決まるので、Go 1.26 でも普通にメソッドにできた。もうひとつは、Map は新しい Stream[F] を返すだけで何も実行しないということ。実際に1回流れるのは Collect() が range した瞬間で、それまでは遅延したままになる。
実測では Stream[E] の underlying が func 型なので、for v := range s は iter.Seq[E] への明示的な変換なしにそのまま通る (go1.27rc3 で確認済み)。また Map(strings.ToUpper) のように関数をそのまま渡せるのは型推論が効いているためで、f func(E) F の F は引数の関数から決まる。
出典
- Go Release Party 1.27 の発表資料リポジトリ
slide/deck.md(stream/stream-useスライド) - 同リポジトリ
memo.md§7 (2026-08-16 追記) - #61898