Scala で型クラス使おうという人ならば一度は目を通したことがあるだろう「空飛ぶサンドイッチのパーツ」。
Scala での型クラスの実装方法といったらたいていこの記事に書いてあるような方式になるのではないでしょうか(というか、私がググった限りではこのパターンしか見つけられなかった)。
でもこの方式、context-bound 型パラメータ使うんですよね。これのせいで def が登場しちゃうし、カリー化ができない。空飛ぶサンドイッチのパーツ自身、関数リテラル使おうぜ! と言いながらこのせいで def 使っちゃう。悔しい。それから、もっと便利にしようということで Scalaz みたいに Ops とか入れちゃって、型クラス実現するのにめんどうなこと!
ということで、Scala 2.10 から入った implicit classes 使うと型クラス簡単ですぜっていうのが今回の話です。空飛ぶサンドイッチのパーツの Depth をやってみます。
まず型クラス。ジェネリクスはいりません。
trait Depth {
val: depth: Int
}
// お好みで関数バージョンも作りましょう
trait DepthFunctions {
val depth: Depth => Int = _.depth
}
そして型インスタンス。
trait DepthInstances {
implicit class TreeDepth(tree: Tree) extends Depth {
val depth: Int = tree match {
case Empty() => 0
case Leaf(_) => 1
case Node(l, r) => 1 + math.max(l.depth, r.depth)
}
}
implicit class ListDepth(list: List[Int]) extends Depth {
val depth: Int = list match {
case xs => xs.size
}
}
}
使い方。空飛ぶサンドイッチのパーツでは、def になっていた halfDepth もこれで晴れて関数リテラルに!
val allInstances = new DepthInstances
with DepthFunctions {}
import allInstances._
val halfDepth: Depth => Int = _.depth / 2
// ↓ 関数バージョンを使えばこんな書き方もできます。
// val halfDepth: Depth => Int = d => depth(d) / 2
halfDepth(List(1, 2, 3, 4))
簡単! やった!
単純なものならこれでいけるけど、Depthが従来の型クラスの形式になっていない(型パラメータをとらない)ので、型クラスを型クラスのまま抽象化して扱う場合に微妙な感じになる気がします。
あと、どちらにしろこのimplicit classのパターンでも、FunctorやMonadなどの高階型の場合には、型クラスの定義側で型パラメータをとるように定義せざるを得ない気がします
コメントありがとうございます!
今Scalaで作っているものがあって、それに適用してみているところなのですが、ダメなところを見つけたらまた記事にしたいと思います。