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
こんなふうにすると、コンパイラに怒られる。