Go 1.23 で導入予定のイテレータでLINQ的な検索の実装を試した

最近は Go 1.23 で導入予定のイテレータを試して使い方を探っています。1 そのなかでイテレータを使ってLINQ的なことができないかを試したところ、良さそうな形になったので紹介します。

LINQは、C#Visual Basic、F#などの.NET系の言語でサポートされている様々なデータソースに対するクエリ機能のことを指します。 LINQを使うと LINQ の概要 - .NET | Microsoft Learnの冒頭にある例のような単純なクエリ構文を使ってデータの変換や検索などが記述できます。

Go 1.23で導入予定のイテレータLINQと同様に連続したデータに対して統一的で簡潔なインターフェイスを与える仕組みなので、イテレータを使うとLINQ的な実装がうまく記述できそうという見込みがありました。

試すために最小限のメソッドしか準備しておらず、実装途中ですがライブラリ形式に纏めてみました。 github.com

使い方はサンプルコードを見るとわかりやすいかと思います。 go-linq/example/select/main.go at main · tomato3713/go-linq · GitHub

Go 1.23 はまだリリースされていないので実行する際はリリース前の機能を簡単に試せる gotip を使います。 結果は次のようになります。宣言時の要素から Where() による要素のフィルタや OrderBy() によるソート、Select() による各要素の加工がされた結果が出力されています。

$ cat example/select/main.go 
package main

import (
        "fmt"
        "github.com/tomato3713/linq"
        "slices"
)

func main() {
        list := []int{5, 3, 10, 1, 33, 4, 12, 11, 15}

        cond1 := func(v int) bool { return v > 5 }
        cond2 := func(v int) bool { return v < 30 }

        cmp := func(a, b int) int { return a - b }

        selector := func(item int) int { return item * 2 }

        q := linq.New(slices.Values(list))

        for v := range q.
                Where(cond1).      // -> []int{10, 33, 12, 11, 15}
                Where(cond2).      // -> []int{10, 12, 11, 15}
                OrderBy(cmp).      // -> []int{10, 11, 12, 15}
                Select(selector) { // -> []int{20, 22, 24, 30}
                // output
                // 20
                // 22
                // 24
                // 30
                fmt.Println(v)
        }
}
$ gotip run example/select/main.go 
20
22
24
30

やっていることは単純で linq.New()で作った iter.Seq[T]と同等の型である query 型のオブジェクトを作り、その query 型をレシーバー及び戻り値に持つメソッドをメソッドチェーンのように連続して呼び出しています。

iter.Seq[T] の形をしていれば汎用的に扱えることやメソッドチェーン的な呼び出しができることでコードの見通しは良いと考えています。 一方、パフォーマンス的な観点では、例えば OrderBy() は処理内容的に仕方がないですが一度全ての要素をスライスに落としてからソートしているのでイテレーターにしたことによるパフォーマンス的な恩恵は受けきれていないかもしれません。

q := linq.New(slices.Values(list))
for v := range q.Where(cond1).Where(cond2).OrderBy(cmp).Select(selector) {
    fmt.Println(v)
}

Go 1.23 がリリースされてイテレータが一般的に利用されるようになるのは、まだ先のことですが色々なイテレータの利用アイディアが見られるのを楽しみにしています。