GoでUnion型/直和型をいい感じに表現する方法

# TL;DR

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

```Go

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

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

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

```
type IntString = Int | String
```

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

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

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

## 型のswitch文を使う

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

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

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

## structを使う

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

```go
type IntStringUnion struct {
	Int    int
	String string
}
```

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

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

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

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

```go
// IntとStringの直和型
type IntStringUnion interface {
	Match(Cases)
}
```

メソッド`Match`に渡す構造体`Cases`は、各型で行う処理を収めた構造体です。

```go
// Int/String型の場合に実行する処理を格納する構造体
type Cases struct {
	Int    func(Int)
	String func(String)
}
```

構造体`Cases`に処理内容を入れて組み立て、メソッド`Match`を呼び出すことで各型ごとの処理へとディスパッチすることができます。

```go
item.Match(Cases{
	Int:    func(i Int) { sum += int(i) },            // Int型だった場合の処理
	String: func(s String) { sum += len(string(s)) }, // String型だった場合の処理
})
```

`IntStringUnion`を構成する元の型、`Int`と`String`は以下のように定義します。
この定義により、それぞれ`IntStringUnion`のインターフェイスを満たすことに注意してください。

```go
type Int int

func (i Int) Match(c Cases) { c.Int(i) } // IntStringUnionインターフェイスの実装

type String string

func (i String) Match(c Cases) { c.String(i) } // IntStringUnionインターフェイスの実装
```

`Int`と`String`共に`IntStringUnion`のインターフェイスを満たすので、以下のような表現が可能となります。

```go
unionArray := []IntStringUnion{String("1"), Int(2), String("123"), Int(4)}
```



全体のコードの例は以下のようになります。

```go
package main

import (
	"fmt"
)
// IntとStringの直和型
type IntStringUnion interface {
	Match(Cases)
}

// Int/String型の場合に実行する処理を格納する構造体
type Cases struct {
	Int    func(Int)
	String func(String)
}

type Int int

func (i Int) Match(c Cases) { c.Int(i) } // IntStringUnionインターフェイスの実装

type String string

func (i String) Match(c Cases) { c.String(i) } // IntStringUnionインターフェイスの実装

func main() {
	// IntStringUnion型からなる配列
	unionArray := []IntStringUnion{String("1"), Int(2), String("123"), Int(4)}

	// 総和を計算する、Int => その値そのまま、String => 文字列の長さ、として各要素を評価して加算する
	sum := 0
	for _, item := range unionArray {
		item.Match(Cases{ // ここで型のパターンマッチを行う
			Int:    func(i Int) { sum += int(i) },            // Int型だった場合の処理
			String: func(s String) { sum += len(string(s)) }, // String型だった場合の処理
		})
	}

	fmt.Printf("%d", sum) // => 10が出力される
}
```

# 補遺

上記の`IntStringUnion`インターフェイスの実装だと例えば`Case.Int`が`nil`の場合ランタイムエラーが起きるので、以下のような実装のほうがより安全でしょう。

```go
type Int int

func (i Int) Match(c Cases) {
	if c.Int != nil {
		c.Int(i)
	}
}
```