https://qiita.com/sxarp/items/cd528a546d1537105b9d

直和型を実装するのではなく、(型の)パターンマッチの方を実装します。 型のパターンマッチはクロージャーを使って以下のように表現できます。


IntOrString.Match(Cases{ // 型のパターンマッチ
    Int:    func(i Int) { sum += int(i) },            // Int型だった場合の処理
    String: func(s String) { sum += len(string(s)) }, // String型だった場合の処理
})

そもそもUnion型/直和型とは

Union型/直和型とはどちらか片方の型の値を取るような型のことで典型的には以下のような構文で表現されます。

この機能を持っている言語としては静的型付け関数型言語(Haskell/OCaml/F#)、強力な型機能のある言語(Rust/TypeScript)などがあります。

Goを使っていても、"複数の型を取りうる型"を考えたくなることがあり、この場合よくやられている方法として次のようなものがあると思います。

GoでUnion型/直和型を表現するこれまでの(あまりイケてない)やり方

型のswitch文を使う

以下のようなvalue.(type)を使った書き方はよく見られます。

switch v := value.(type) {
case string:
    sum += len(v)
case int:
    sum += v
}

ここでの問題はvalueinterface{}として扱う必要があり、型による保証を受けられず型安全ではありません

structを使う

型を要素として含むようなstructを作るやり方もよく見られます。

この方法の問題点は不整合な値が許容されることです。 具体的には複数のフィールドに非ゼロな値が入ってしまうことが可能で、こうなってしまうともやは直和型の値とは呼べません。

v := IntStringUnion{
    Int:    10,
    String: "hoge",
}

また、元となる型のzero-valueをどう表現するのかという問題もあります。

今回提案する表現方法

直和型を利用する際には、パターンマッチを使って元の型を取り出して処理しますが、このような処理を実装することを考えます。 具体的には以下のようなパターンマッチを表現するメソッドMatchを持った型(インターフェイス)を考えます。