Skip to content

Union and generic types

MkUnion will generate generic unions for you.

For example, let's say you want to create a recursive tree data structure, that in its leaves will hold a value of A type.

Declaration and generation

You can use mkunion to create a union type for the tree:

example/tree.go
//go:tag mkunion:"Tree[A]"
type (
    Branch[A any] struct{ L, R Tree[A] }
    Leaf[A any]   struct{ Value A }
)

After you run generation (as described in getting started), you have access to the same features as with non-generic unions.

When defining generic unions, you must follow these requirements:

  1. Type parameters must be specified in the tag: The union tag must include all type parameters used by the variant types.
  2. Parameter names must match: Type parameter names in the tag must match those used in variant types both by name and position.
  3. Same number of parameters: Each variant type needs to have the same number of type parameters.

Matching function

Let's define a higher-order function ReduceTree that will traverse leaves in Tree and produce a single value.

This function uses MatchTreeR1 function that is generated automatically for you.

example/tree.go
func ReduceTree[A, B any](x Tree[A], f func(A, B) B, init B) B {
    return MatchTreeR1(
        x,
        func(x *Branch[A]) B {
            return ReduceTree(x.R, f, ReduceTree(x.L, f, init))
        }, func(x *Leaf[A]) B {
            return f(x.Value, init)
        },
    )
}

Example usage

You can use such function to sum all values in the tree, assuming that tree is of type Tree[int]:

example/tree_test.go
func Example_treeSumValues() {
    tree := &Branch[int]{
        L: &Leaf[int]{Value: 1},
        R: &Branch[int]{
            L: &Branch[int]{
                L: &Leaf[int]{Value: 2},
                R: &Leaf[int]{Value: 3},
            },
            R: &Leaf[int]{Value: 4},
        },
    }

    result := ReduceTree(tree, func(x int, agg int) int {
        return agg + x
    }, 0)

    fmt.Println(result)
    // Output: 10
}

You can also reduce the tree to a complex structure, for example, to keep track of the order of values in the tree, along with the sum of all values in the tree.

example/tree_test.go
type orderAgg struct {
    Order  []int
    Result int
}

func Example_treeCustomReduction() {
    tree := &Branch[int]{
        L: &Leaf[int]{Value: 1},
        R: &Branch[int]{
            L: &Branch[int]{
                L: &Leaf[int]{Value: 2},
                R: &Leaf[int]{Value: 3},
            },
            R: &Leaf[int]{Value: 4},
        },
    }

    result := ReduceTree(tree, func(x int, agg orderAgg) orderAgg {
        return orderAgg{
            Order:  append(agg.Order, x),
            Result: agg.Result + x,
        }
    }, orderAgg{
        Order:  []int{},
        Result: 0,
    })
    fmt.Println(result.Order)
    fmt.Println(result.Result)
    // Output: [1 2 3 4]
    // 10
}

Next steps