Implicit Parameters

"Scala By Example" http://www.scala-lang.org/docu/files/ScalaByExample.pdf
の15章から
Implicit Parameters について、やっと理解した。


まず、以下のような抽象クラスがある。

abstract class SemiGroup[A] {
  def add(x: A, y: A): A
}

abstract class Monoid[A] extends SemiGroup[A] {
  def unit: A
}

SemiGroup, Monoidという階層構造になっていることは、本質的ではない。
当然、1つの抽象クラスにaddとunitが定義されていても構わない。
要は、こういったクラス階層の中でもImplicitParametersがうまく働く、
ということを示している。


これを継承する実装クラス(シングルトンオブジェクト)が2つある。

object StringMonoid extends Monoid[String] {
  def add(x: String, y: String): String = x concat y
  def unit: String = ""
}

object IntMonoid extends Monoid[int] {
  def add(x: Int, y: Int): Int = x + y
  def unit: Int = 0
}

そして、(型パラメータに実際の型を指定せずに、型について抽象性をたもったまま)
抽象クラスMonoidを使うメソッドsumはこんな実装になるだろう。

def sum[A](xs: List[A], m: Monoid[A]): A =
  if (xs.isEmpty) m.unit else m.add(xs.head, sum(xs.tail,m))

使い方は、

println(sum(List(1, 2, 3), IntMonoid))
println(sum(List("a", "b", "c"), StringMonoid))

の様になる。
つまり、Monoidの実装クラスをいちいち渡してやらなくてはいけない。
これでは、いかにもカッコ悪いし、使いにくい。


そこで、「Implicit Parameters」!!!


まず、sumをこの様に改良する。

def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
  if (xs.isEmpty) m.unit else m.add(xs.head, sum(xs.tail)(m))

変更点は2つ

  • Listだけを適用した状態(カリー化した状態)をつくれるようにした
  • Monoidの実装クラスを表す引数mに「implicit」を指定した

使い方も以下のように変化する。

println(sum(List(1, 2, 3))(IntMonoid))
println(sum(List("a", "b", "c"))(StringMonoid))

次に、実装クラスについてもちょっとした修正を加える。

implicit object StringMonoid extends Monoid[String] {
  def add(x: String, y: String): String = x concat y
  def unit: String = ""
}

implicit object IntMonoid extends Monoid[int] {
  def add(x: Int, y: Int): Int = x + y
  def unit: Int = 0
}

修正点は、ただ「implicit」宣言をした「だけ」


この結果、以下のような使い方が出来るようになる。

println(sum(List(1, 2, 3)))
println(sum(List("a", "b", "c")))

なるほど、便利。


ちなみに、実装クラスをobjectでなく、普通のclassにしたい場合は、
それをインスタンス化する時にimplicitすればよい

class StringMonoid extends Monoid[String] {
  def add(x: String, y: String): String = x concat y
  def unit: String = ""
}

class IntMonoid extends Monoid[int] {
  def add(x: Int, y: Int): Int = x + y
  def unit: Int = 0
}

implicit val stringMonoid = new StringMonoid
implicit val intMonoid = new IntMonoid

もちろん、

implicit val intMonoid = new IntMonoid
implicit val intMonoid2 = new IntMonoid

こんなふうにすると、コンパイラに怒られる。