> Source: https://www.ymotongpoo.com/works/goblog-ja/deconstructing-type-parameters/


# 型パラメータを分解する

[Deconstructing Type Parameters](https://go.dev/blog/deconstructing-type-parameters) by Ian Lance Taylor

## sliceパッケージの関数シグネチャ

[`slices.Clone`](https://pkg.go.dev/slices#Clone) 関数はとてもシンプルです。任意の型のスライスのコピーを作成します。

```go
func Clone[S ~[]E, E any](s S) S {
    return append(s[:0:0], s...)
}
```

これがうまく動作するのは、容量がゼロのスライスに対してappendを行うと、新しいバッキング配列が確保されるためです。
関数の本体はシグネチャよりも短くなっていますが、これは本体自体が短いことに加えて、シグネチャが長いことにもよります。
この記事では、なぜこのようなシグネチャになっているのかを説明します。

## シンプルなClone

まずはシンプルなジェネリックのClone関数を書くところから始めましょう。これはsliceパッケージにあるものではありません。
任意の要素型のスライスを受け取り、新しいスライスを返す関数が欲しいとします。

```go
func Clone1[E any](s []E) []E {
    // 本体は省略
}
```

このジェネリック関数`Clone1`は、型パラメータ`E`を1つだけ持っています。引数として型`E`のスライスである`s`を1つ受け取り、
同じ型のスライスを返します。このシグネチャは、Goのジェネリクスに慣れている人であれば誰でも理解しやすいものです。

しかし、ここには問題があります。名前付きのスライス型はGoではあまり一般的ではありませんが、実際に使われることがあります。

```go
// MySliceは特別なStringメソッドを持つ文字列のスライスです。
type MySlice []string

// Stringは、MySlice値を表示用の文字列に変換して返します。
func (s MySlice) String() string {
    return strings.Join(s, "+")
}
```

`MySlice`のコピーを作り、その文字列をソートした状態で表示用の文字列を取得したいとします。

```go
func PrintSorted(ms MySlice) string {
    c := Clone1(ms)
    slices.Sort(c)
    return c.String() // コンパイルエラーになる
}
```

残念ながら、これはうまくいきません。コンパイラは次のようなエラーを報告します。

```
c.String undefined (type []string has no field or method String)
```

型パラメータを型引数に置き換えて`Clone1`を手動でインスタンス化してみると、問題が見えてきます。

```go
func InstantiatedClone1(s []string) []string
```

[Goの代入規則](/ref/spec#Assignability)により、型`MySlice`の値を型`[]string`の引数に渡せるので、
`Clone1`を呼び出すこと自体は問題ありません。しかし、`Clone1`が返すのは型`[]string`の値であって、型`MySlice`の値ではありません。
型`[]string`には`String`メソッドがないため、コンパイラがエラーを報告するのです。

## 柔軟なClone

この問題を解決するには、引数と同じ型を返すバージョンの`Clone`を書く必要があります。それができれば、
型`MySlice`の値で`Clone`を呼び出したときに、結果として型`MySlice`の値が返るようになります。

次のような形になるはずだとわかります。

```go
func Clone2[S ?](s S) S // 無効
```

この`Clone2`関数は、引数と同じ型の値を返します。

ここでは制約を`?`と書いていますが、これは単なるプレースホルダーです。これを実際に動かすには、関数の本体を書けるような
制約を用意する必要があります。`Clone1`では要素型の制約として単に`any`を使えばよかったのですが、`Clone2`ではそれでは
うまくいきません。`s`がスライス型であることを要求したいからです。

スライスが欲しいとわかっているので、`S`の制約はスライスでなければなりません。スライスの要素型が何であるかは気にしないので、
`Clone1`のときと同様に`E`と呼ぶことにしましょう。

```go
func Clone3[S []E](s S) S // 無効
```

これはまだ無効です。なぜなら`E`を宣言していないからです。`E`の型引数はどんな型でもよいので、`E`そのものも型パラメータで
なければなりません。どんな型でもよいので、その制約は`any`になります。

```go
func Clone4[S []E, E any](s S) S
```

これでだいぶ近づいてきて、少なくともコンパイルは通りますが、まだ完全ではありません。このバージョンをコンパイルして
`Clone4(ms)`を呼び出すと、エラーが発生します。

```
MySlice does not satisfy []string (possibly missing ~ for []string in []string)
```

コンパイラは、型パラメータ`S`に型引数`MySlice`を使えないと言っています。なぜなら`MySlice`は制約`[]E`を満たさないからです。
これは、制約としての`[]E`が`[]string`のようなスライスの型リテラルのみを許可し、`MySlice`のような名前付き型を許可しないためです。

## 基底型の制約

エラーメッセージが示唆しているように、答えは`~`を追加することです。

```go
func Clone5[S ~[]E, E any](s S) S
```

繰り返しになりますが、型パラメータと制約を`[S []E, E any]`のように書くと、`S`の型引数は名前のないスライス型であれば
何でもよいものの、スライスリテラルとして定義された名前付き型にはなれません。`~`を付けて`[S ~[]E, E any]`のように書くと、
`S`の型引数は基底型がスライス型であるどんな型でもよくなります。

名前付き型`type T1 T2`があるとき、`T1`の基底型は`T2`の基底型と同じです。`int`のような定義済み型や`[]string`のような
型リテラルの基底型は、その型自身です。正確な詳細については[言語仕様](/ref/spec#Underlying_types)を参照してください。
今回の例では、`MySlice`の基底型は`[]string`です。

`MySlice`の基底型はスライスなので、型`MySlice`の引数を`Clone5`に渡せます。お気づきかもしれませんが、`Clone5`の
シグネチャは`slices.Clone`のシグネチャと同じです。ついに目指していた形にたどり着きました。

先に進む前に、なぜGoの構文が`~`を要求するのかについて考えてみましょう。`MySlice`を渡せるようにするのが常に望ましいので
あれば、それをデフォルトの挙動にしてもよいのではないかと思うかもしれません。あるいは、厳密な一致をサポートする必要が
あるなら、逆に、制約`[]E`のほうが名前付き型を許可し、たとえば`=[]E`のような制約がスライスの型リテラルのみを許可する
ようにしてもよいのではないか、とも考えられます。

これを説明するために、まず`[T ~MySlice]`のような型パラメータリストには意味がないことを確認しましょう。というのも、
`MySlice`は他のどの型の基底型にもなっていないからです。たとえば、`type MySlice2 MySlice`という定義があった場合、
`MySlice2`の基底型は`MySlice`ではなく`[]string`になります。したがって、`[T ~MySlice]`はどんな型も許可しないか、
あるいは`[T MySlice]`と同じ意味になって`MySlice`だけにマッチするか、のどちらかになってしまいます。どちらにしても、
`[T ~MySlice]`は有用ではありません。この混乱を避けるため、Goの言語仕様では`[T ~MySlice]`を禁止しており、
コンパイラは次のようなエラーを出します。

```
invalid use of ~ (underlying type of MySlice is []string)
```

もしGoがチルダを要求せず、`[S []E]`が基底型が`[]E`であるどんな型にもマッチするとしたら、`[S MySlice]`の意味を
定義する必要が出てきます。

`[S MySlice]`を禁止するか、あるいは`[S MySlice]`は`MySlice`にしかマッチしないとすることもできますが、どちらの
アプローチも定義済み型に関して問題にぶつかります。`int`のような定義済み型は、それ自身が自分の基底型です。基底型が
`int`であるどんな型引数でも受け付ける制約を書けるようにしたいわけです。現在の言語仕様では、`[T ~int]`と書くことで
それが可能です。チルダを要求しないとすると、「基底型が`int`であるどんな型」かを表す方法が依然として必要になります。
それを表す自然な書き方は`[T int]`でしょう。そうなると、`[T MySlice]`と`[T int]`は、見た目はとてもよく似ているのに、
異なる挙動をすることになってしまいます。

`[S MySlice]`が「基底型が`MySlice`の基底型と同じであるどんな型にもマッチする」という意味だとすることもできるかも
しれませんが、それでは`[S MySlice]`という書き方自体が不要で紛らわしいものになってしまいます。

私たちは、`~`を要求し、型そのものではなく基底型にマッチさせているのがどこなのかを明確にするほうがよいと考えています。

## 型推論

`slices.Clone`のシグネチャについて説明したところで、実際に`slices.Clone`を使う際に型推論によってどのように
簡潔になるかを見てみましょう。`Clone`のシグネチャは次の通りでした。

```go
func Clone[S ~[]E, E any](s S) S
```

`slices.Clone`の呼び出しでは、パラメータ`s`にスライスが渡されます。単純な型推論によって、コンパイラは型パラメータ
`S`の型引数が`Clone`に渡されたスライスの型であると推論できます。さらに型推論は、`S`に渡された型引数の要素型が`E`の
型引数であることも見抜けるだけの能力を持っています。

つまり、次のように書けます。

```go
    c := Clone(ms)
```

次のように書く必要はありません。

```go
    c := Clone[MySlice, string](ms)
```

`Clone`を呼び出さずに参照するだけの場合は、コンパイラが推論に使える情報がないため、`S`の型引数を指定する必要が
あります。幸い、その場合でも型推論は`S`の引数から`E`の型引数を推論できるので、`E`を別途指定する必要はありません。

つまり、次のように書けます。

```go
    myClone := Clone[MySlice]
```

次のように書く必要はありません。

```go
    myClone := Clone[MySlice, string]
```

## 型パラメータを分解する

ここまで使ってきた一般的な手法、つまり型パラメータ`S`を別の型パラメータ`E`を使って定義するというやり方は、
ジェネリック関数のシグネチャの中で型を分解する手法です。型を分解することで、その型のあらゆる側面に名前を付け、
制約をかけられるようになります。

たとえば、`maps.Clone`のシグネチャは次のようになっています。

```go
func Clone[M ~map[K]V, K comparable, V any](m M) M
```

`slices.Clone`と同様に、パラメータ`m`の型に型パラメータを使い、さらに別の2つの型パラメータ`K`と`V`を使って
その型を分解しています。

`maps.Clone`では、マップのキー型に必要とされる通り、`K`を`comparable`に制約しています。構成要素となる型は、
好きなように制約をかけられます。

```go
func WithStrings[S ~[]E, E interface { String() string }](s S) (S, []string)
```

これは、`WithStrings`の引数が、要素型が`String`メソッドを持つスライス型でなければならないことを表しています。

Goのすべての型は構成要素となる型から組み立てられるため、型パラメータを使ってそれらの型を分解し、好きなように
制約をかけることが常に可能です。

By Ian Lance Taylor

