Scalaで多くの型引数が登場する複雑なクラスを実装する

### 1. はじめに

Scalaを用いたアプリケーションコードでは、`型パラメータ`を用いたくなることがあるかと思いますが
その中でも特に、`多く(例えば5つ)の型引数が登場するようなクラスを実装したい`
というモチベーションが湧くことがあり、この記事ではその実装の工夫について紹介してみようと思います。

具体的なコードを書いて例を示すと、以下のような
抽象クラスの中で`多く(5つ)の抽象型を取るようなクラスを実装したい`時のことです。

```scala:ManyTypeArgumentCoding.scala
// Foo~Quuxは、各々Traitとして定義されているものとする
trait Hoge[Foo, Bar, Baz, Qux, Quux] {
  val foo: Foo = ???
  val bar: Bar = ???
  def baz: Baz = ???
  def qux: Qux
  def quux: Quux
}
```

### 2. 素朴な実装
素朴に書くと、上記`trait Hoge`を継承する、 `Hoge1`, `Hoge2`, … クラスは
以下のような書き方になるかと思います。

```scala:SimpleManyTypeArgumentCoding.scala
// 型引数をいっぱい取るととても長い、イケてない実装に(右にスクロールして下さい…)
class Hoge1[Foo1 <: Foo, Bar1 <: Bar, Baz1 <: Baz, Qux1 <: Qux, Quux1 <: Quux]() extends Hoge[Foo1, Bar1, Baz1, Qux1, Quux1] {
  override val baz = ???
  // 以下はoverride修飾子を付けなくてもオーバーライドされる
  override val qux = ???
  override val quux = ???
}

case class Foo1() extends Foo
case class Bar1() extends Bar
case class Baz1() extends Baz
case class Qux1() extends Qux
case class Quux1() extends Quux
```

ただ上記の書き方だと、1行目のclass宣言が長くなりとても読みやすいコードとは言えません。
加えて、`classの宣言が可読性が低い`＋`複雑に入り組んだ実装である`
という背景から、以下のようなバグが混入することも起こり得ると思います。

```scala:WrongTypeArgumentCoding.scala
// Bar1と間違えて、Bar2を抽象クラスの型引数に渡してしまっている
// この単純な実装例では、コンパイルが通ってしまう
// 3つ目(Baz)以降は簡略化のため???としています(本当は動きませんが、分かりやすさ重視で)
class Hoge1[Foo1 <: Foo, Bar2 <: Bar, ???]() extends Hoge[Foo1, Bar2, ???] {
  override val baz = ???
  override val qux = ???
  override val quux = ???
}
```

また、型引数を扱うクラスが増えてくると、クラス`Hoge1`では正しく`Bar1`を型引数として渡したが、
別クラス`Fuga1`では誤って`Bar2`を渡してしまった、といったややこしいことも起こり得ます。


### 3. 紹介したい実装
以下のような実装が良いと考えています。

#### step1.
まず、抽象クラス`Foo`に対してその他の型を`type alias`として定義し、
具象クラス`Foo1`で具体的な`type alias`の値を代入します。

```scala:Foo.scala
// Fooの中で、Bar~Quuxをtypeとして定義する
trait Foo {
  // TはTypeの略(抽象クラスと名前が被らないように)
  type TBar <: Bar
  type TBaz <: Baz
  type TQux <: Qux
  type TQuux <: Quux
}
```
```scala:Foo1.scala
// Foo1に対応する、Bar1~Quux1をオーバーライドする
case class Foo1() extends Foo {
  type TBar = Bar1
  type TBaz = Baz1
  type TQux = Qux1
  type TQuux = Quux1
}
```

#### step2.
抽象クラス`Hoge`で、`trait Foo`から、各々の`type alias`へアクセスします。
※ Scalaでは、具象クラスの`type alias`へのアクセスは`.`を使い、抽象クラスの`type alias`へのアクセスは`#`を用います。

```scala:Hoge.scala
trait Hoge[Foo] {
  val foo: Foo = ???
  // 抽象クラスFooから、typeにアクセス
  val bar: Foo#TBar = ???
  def baz: Foo#TBaz = ???
  def qux: Foo#TQux
  def quux: Foo#TQuux
}
```

#### step3.
具象クラス`Hoge1`を定義します。必要な型引数は`Foo`だけになっていることが分かります。

```scala:Hoge1.scala
class Hoge1[Foo1 <: Foo]() extends Hoge[Foo1] {
  override val baz = ???
  override val qux = ???
  override val quux = ???
}
```

### 4. まとめ

メリットとして、以下の2点が挙げられます。

#### 利点1. 型引数が減ったことで読みやすい実装になった

`2. 素朴な実装`で示した、`SimpleManyTypeArgumentCoding.scala`と比較して、
`3. 紹介したい実装` では、`Hoge1.scala`の型引数が減ったことで読みやすい実装になりました。

#### 利点2. 誤った型引数を渡してしまうリスクが軽減された

`3. 紹介したい実装`の`Class Hoge1` では、`case class Foo1`の`type alias`に紐づいた型が使われ
`2. 素朴な実装`で示した`WrongTypeArgumentCoding.scala`のような
`誤った型引数を渡してしまう`リスクが低くなります。

また、別クラス`Fuga1`でも、同様に`case class Foo1`の`type alias`に紐づいた型が使われていて
堅牢な型の縛りが効いていると言えます。

本編は以上です。
ご指摘ご意見等あればコメント宜しくお願いします。

### 5. 補足
読み飛ばしてしまって問題ないです。

Scalaの抽象クラス(今回の例でいえば`trait Hoge`)の実装を
具象クラス(今回の例でいえば`class Hoge1`)の実装でオーバーライドする場合には、
`val`ではなく、`def`を使うことが推奨されます。

理由はシンプルで、抽象クラス自体が初期化時されたタイミングで、`val`が初期化されてしまうためです。
具象クラスがオーバーライドせずに、`null`として初期化されることが起こり得るので推奨されません。

`def`を用いて、具象クラスでオーバーライドするメソッドから呼ばれたタイミングで
初期化されるようにした方が無難でしょう。

また、上記の理由から、
抽象クラスでも、具象クラスの実装がオーバーライドされない場合は`val`を用いても良いでしょう。
具象クラスで、抽象クラスの実装をオーバーライドする場合には`val`を用いた方が良いでしょう。

終わり