是否可以在Scala中实现liftM2?

时间:2011-12-03 08:31:11

标签: scala haskell monads typeclass lifting

在Haskell中,liftM2可以定义为:

liftM2 :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
liftM2 f m1 m2 = do
  x1 <- m1
  x2 <- m2
  return $ f x1 x2

我想把它翻译成Scala。我的第一次尝试如下:

def liftM2[T1, T2, R, M[_]](f: (T1, T2) => R)(ma: M[T1], mb: M[T2]) : M[R] = for {
  a <- ma
  b <- mb
} yield f(a, b)

我认为这是最明显的方法:“flat flatMap不是类型参数M [T1]的成员”。是的,我没有说明M[_]是某种单子。所以接下来我尝试的是定义一些结构类型,如:

type Monad[A] = {
  def flatMap[B](f: (A) => Monad[B]): Monad[B]
}

......并拥有M[A] <: Monad[A]。但这不起作用,因为Scala没有递归结构类型。

所以我尝试的下几件事涉及与M[A] <: FilterMonadic[A, _]相似的回转。那些都失败了,可能是因为我无法找出CanBuildFrom的正确隐含函数。

我在StackOverflow上找到的最密切相关的问题是this one,它涉及递归结构类型以及如何在Scala中模仿Haskell的类型类。但是这种方法需要定义从你关心的每个类型到定义类型类的特征的隐式转换,在这种情况下这似乎非常循环......

有什么好办法可以做我想做的事吗?

1 个答案:

答案 0 :(得分:31)

在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方法 not 已在面向对象的接口中定义,因为它没有M[A]作为其中一个要转换为{的参数{1}} OO编码中的引用。 (或换句话说:它不能成为接口的一部分,原因与构造函数签名无法在接口中表示相同。)

然后您可以将this写为:

liftM2

此模式已广泛应用于Scalaz。目前正在开发中的Version 7包括index of the type classes

除了为标准库类型提供类型类和实例外,它还提供了一个“语法”层,允许更熟悉的 receiver.method(args)样式的方法调用。这通常提供更好的类型推断(考虑到Scala的从左到右的推理算法),并允许使用for-comprehension语法糖。下面,我们使用它来重写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) ,基于liftM2中的mapflatMap方法。

MonadV

<强>更新

是的,可以为Scala集合编写不太通用的// 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个实例。

CanBuildFrom