在 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中进行了更深入的探索。请注意,该point
方法不能在面向对象的接口中定义,因为它没有M[A]
将其转换为this
OO 编码中的引用的参数之一。(或者换一种说法:它不能成为接口的一部分,原因与构造函数签名不能在接口中表示的原因相同。)
然后你可以写成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 版包括类型 classes 的索引。
除了为标准库类型提供类型类和实例之外,它还提供了一个“语法”层,允许更熟悉的方法调用receiver.method(args)风格。这通常提供更好的类型推断(考虑到 Scala 的从左到右推断算法),并允许使用 for-comprehension 语法糖。下面,我们用它来重写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)