シンプルなツリーセット

動機となる例として、二分探索木のジェネリック版が必要だと仮定しましょう。このツリーに格納される要素は順序付けが必要なので、型パラメータには順序を決定する制約が必要です。シンプルな選択肢は、Go 1.21で導入された cmp.Ordered 制約を使用することです。これは型パラメータを順序付け可能な型(文字列と数値)に制限し、その型のメソッドが組み込みの順序演算子を使用できるようにします。

// Treeのゼロ値は使用可能な空のツリーです。
type Tree[E cmp.Ordered] struct {
    root *node[E]
}

func (t *Tree[E]) Insert(element E) {
    t.root = t.root.insert(element)
}

type node[E cmp.Ordered] struct {
    value E
    left  *node[E]
    right *node[E]
}

func (n *node[E]) insert(element E) *node[E] {
    if n == nil {
        return &node[E]{value: element}
    }
    switch {
    case element < n.value:
        n.left = n.left.insert(element)
    case element > n.value:
        n.right = n.right.insert(element)
    }
    return n
}

しかし、このアプローチには < が定義されている基本型でのみ動作するという欠点があります。time.Time のような構造体型は挿入できません。

これを解決するために、ユーザーに比較関数を提供してもらうことができます:

// FuncTreeはNewTreeFuncで作成する必要があります。
type FuncTree[E any] struct {
    root *funcNode[E]
    cmp  func(E, E) int
}

func NewFuncTree[E any](cmp func(E, E) int) *FuncTree[E] {
    return &FuncTree[E]{cmp: cmp}
}

func (t *FuncTree[E]) Insert(element E) {
    t.root = t.root.insert(t.cmp, element)
}

type funcNode[E any] struct {
    value E
    left  *funcNode[E]
    right *funcNode[E]
}

func (n *funcNode[E]) insert(cmp func(E, E) int, element E) *funcNode[E] {
    if n == nil {
        return &funcNode[E]{value: element}
    }
    sign := cmp(element, n.value)
    switch {
    case sign < 0:
        n.left = n.left.insert(cmp, element)
    case sign > 0:
        n.right = n.right.insert(cmp, element)
    }
    return n
}

これは動作しますが、欠点もあります。比較関数を明示的に初期化する必要があるため、コンテナ型のゼロ値を使用できなくなります。また、関数フィールドの使用により、コンパイラが比較呼び出しをインライン化することが困難になり、大幅な実行時オーバーヘッドが発生する可能性があります。

要素型でメソッドを使用すると、これらの問題を解決できます。メソッドは型に直接関連付けられているためです。メソッドは明示的に渡す必要がなく、コンパイラは呼び出しのターゲットを確認してインライン化できる可能性があります。しかし、要素型が必要なメソッドを提供することを要求する制約をどのように表現できるでしょうか?