Implicit Parameter (2)

Implicit Parameter のもう1つの使い方。順を追ってみていく。

何らかのリストの合計を求めるメソッド sum を考える。ポイントは何のリストかわからない(あるいは何のリストでもいいように)、というところだ。

Scala は静的型付け言語なので単純にはいかない。ではどうするかというと、まず、「足し合わせることができる」型を考える。Additive としよう。

scala> trait Additive[A] {
     |     def plus(a: A, b: A): A
     |     def zero: A
     | }
defined trait Additive

Additive は型パラメータを持っていて、これが目的のリストに要素の型になる。また、実装はしていないがメソッドを2つ宣言している。

  • plus :2つの値を足し合わせる
  • zero :ゼロに相当する値を返す

だ。

次に、この型を継承して、具体的な型についてメソッドを実装する。String と Int について実装しよう。

scala> object StringAdditive extends Additive[String] {
     |     def plus(a: String, b: String): String = a + b
     |     def zero: String = ""
     | }
defined object StringAdditive

scala> object IntAdditive extends Additive[Int] {
     |     def plus(a: Int, b: Int): Int = a + b
     |     def zero: Int = 0
     | }
defined object IntAdditive

そして最後に sum メソッド。Additive を使って、足し合わせるように実装する。

scala> def sum[A](lst: List[A])(m: Additive[A]) = lst.foldLeft(m.zero)((x, y) => m.plus(x, y))
sum: [A](lst: List[A])(m: Additive[A])A

これで出来上がり。2つ目の引数リストで Additive を受け取るところがポイント。使い方はこうする。

scala> sum(List(1, 2, 3))(IntAdditive)
res0: Int = 6

scala> sum(List("abc", "def", "ghi"))(StringAdditive)
res1: String = abcdefghi

うまく「合計」を計算できた。

さて、ここからが本題。何のリストを合計するのかはリストを見ればわかるのだから、StringAdditive とか IntAdditive を明示的に渡さなくてもうまくやってほしい。Implicit Parameter を使うとそれができる。

次のように、StringAdditive を IntAdditive の定義の前と、sum メソッドの最後に引数リストの m の前に implicit キーワードをつける。

scala> trait Additive[A] {
     |     def plus(a: A, b: A): A
     |     def zero: A
     | }
defined trait Additive

scala> implicit object StringAdditive extends Additive[String] {
     |     def plus(a: String, b: String): String = a + b
     |     def zero: String = ""
     | }
defined object StringAdditive

scala> implicit object IntAdditive extends Additive[Int] {
     |     def plus(a: Int, b: Int): Int = a + b
     |     def zero: Int = 0
     | }
defined object IntAdditive

scala> def sum[A](lst: List[A])(implicit m: Additive[A]) = lst.foldLeft(m.zero)((x, y) => m.plus(x, y))
sum: [A](lst: List[A])(implicit m: Additive[A])A

これで StringAdditive や IntAdditive を明示的に渡さなくてもよくなる。

scala> sum(List(1, 2, 3))
res0: Int = 6

scala> sum(List("abc", "def", "ghi"))
res1: String = abcdefghi

このとおり。