Scala で型クラスをエンコードする通常の方法は、Haskell に非常に厳密に従っていることがわかります。(オブジェクト指向言語で期待されるように) インターフェイスをList
実装せず、別のオブジェクトで型クラスのインスタンスを定義します。Monad
trait Monad[M[_]] {
def point[A](a: => A): M[A]
def bind[A, B](ma: M[A])(f: A => M[B]): M[B]
def map[A, B](ma: M[A])(f: A => B): M[B] = bind(ma)(a => point(f(a)))
}
implicit object listMonad extends Monad[List] {
def point[A](a: => A) = List(a)
def bind[A, B](ma: List[A])(f: A => List[B]) = ma flatMap f
}
このアイデアはPoor Man's Type Classesで紹介され、 Type Classes as Objects and Implicitsでより深く探求されています。このメソッドは、OO エンコーディングで参照に変換される引数の 1 つを持たないため、オブジェクト指向インターフェイスで定義point
できなかったことに注意してください。(または別の言い方をすると、コンストラクターの署名をインターフェイスで表現できないのと同じ理由で、インターフェイスの一部にすることはできません。)M[A]
this
次に、次のように記述できますliftM2
。
def liftM2[M[_], A, B, C](f: (A, B) => C)
(implicit M: Monad[M]): (M[A], M[B]) => M[C] =
(ma, mb) => M.bind(ma)(a => M.map(mb)(b => f(a, b)))
val f = liftM2[List, Int, Int, Int](_ + _)
f(List(1, 2, 3), List(4, 5)) // List(5, 6, 6, 7, 7, 8)
このパターンはScalazで広く適用されています。現在開発中のバージョン 7には、型クラスのインデックスが含まれています。
標準ライブラリ型の型クラスとインスタンスを提供することに加えて、メソッド呼び出しのより使い慣れたreceiver.method(args)スタイルを可能にする「構文」レイヤーを提供します。これにより、多くの場合、より優れた型推論 (Scala の左から右への推論アルゴリズムを考慮) が可能になり、理解のための構文糖衣の使用が可能になります。以下では、 のとメソッドにliftM2
基づいて を書き換えるためにそれを使用します。map
flatMap
MonadV
// Before Scala 2.10
trait MonadV[M[_], A] {
def self: M[A]
implicit def M: Monad[M]
def flatMap[B](f: A => M[B]): M[B] = M.bind(self)(f)
def map[B](f: A => B): M[B] = M.map(self)(f)
}
implicit def ToMonadV[M[_], A](ma: M[A])
(implicit M0: Monad[M]) =
new MonadV[M, A] {
val M = M0
val self = ma
}
// Or, as of Scala 2.10
implicit class MonadOps[M[_], A](self: M[A])(implicit M: Monad[M]) {
def flatMap[B](f: A => M[B]): M[B] = M.flatMap(self)(f)
def map[B](f: A => B): M[B] = M.map(self)(f)
}
def liftM2[M[_]: Monad, A, B, C](f: (A, B) => C): (M[A], M[B]) => M[C] =
(ma, mb) => for {a <- ma; b <- mb} yield f(a, b)
アップデート
liftM2
そうです、Scala コレクションの一般的でないバージョンを書くことは可能です。必要なすべてのCanBuildFrom
インスタンスをフィードするだけです。
scala> def liftM2[CC[X] <: TraversableLike[X, CC[X]], A, B, C]
| (f: (A, B) => C)
| (implicit ba: CanBuildFrom[CC[A], C, CC[C]], bb: CanBuildFrom[CC[B], C, CC[C]])
| : (CC[A], CC[B]) => CC[C] =
| (ca, cb) => ca.flatMap(a => cb.map(b => f(a, b)))
liftM2: [CC[X] <: scala.collection.TraversableLike[X,CC[X]], A, B, C](f: (A, B) => C)(implicit ba: scala.collection.generic.CanBuildFrom[CC[A],C,CC[C]], implicit bb: scala.collection.generic.CanBuildFrom[CC[B],C,CC[C]])(CC[A], CC[B]) => CC[C]
scala> liftM2[List, Int, Int, Int](_ + _)
res0: (List[Int], List[Int]) => List[Int] = <function2>
scala> res0(List(1, 2, 3), List(4, 5))
res1: List[Int] = List(5, 6, 6, 7, 7, 8)