【问题标题】:Is it possible to implement liftM2 in Scala?是否可以在 Scala 中实现 liftM2?
【发布时间】:2011-12-03 08:31:11
【问题描述】:

在 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)

我猜这可能是最明显的方式失败:“值 flatMap 不是类型参数 M[T1] 的成员”。对,我没有指出M[_] 是某种单子。所以接下来我尝试定义一些结构类型,例如:

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

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

所以接下来我尝试的几件事涉及类似于M[A] &lt;: FilterMonadic[A, _] 的旋转。这些都失败了,可能是因为我无法为CanBuildFrom 找出正确的隐式fu。

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

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

【问题讨论】:

  • 隐式转换方法有其缺陷,但被广泛使用。它是最接近 Haskell 中的 instance 声明的类比(并且,当更多“Scalaish”解决方案失败时,这是最可靠的最后手段)。

标签: scala haskell monads typeclass lifting


【解决方案1】:

在 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 中广泛应用。 Version 7,目前正在开发中,包括一个index of the type classes

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

// 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)

更新

是的,可以为 Scala 集合编写不太通用的 liftM2 版本。您只需输入所有必需的 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)

【讨论】:

  • 非常感谢,杰森!我听说过有关 scalaz-7 的好消息。但我还是有点难过:我想使用liftM2 的东西是显而易见的,比如ListOption,它们已经对mapflatMap 有了很好的定义。有没有办法定义liftM2 直接使用它们?
  • 非常好的解释,谢谢。我时不时地用 Scala 玩了一段时间,这篇文章澄清了一些以前非常不透明的地方。
  • 我可能会补充一点,Haskell 并没有尽可能好地实现这一点。 liftM2 只需要 () 和 map。点操作不是必需的,它经常“妨碍”(有 () 但没有点的函子)。 Haskell 得到的最接近的是 liftA2,它是根据 () 和 point (aka pure) 定义的。这篇文章使用 Scala blog.tmorris.net/lifting 说明了这一点
猜你喜欢
  • 2015-03-28
  • 2019-07-22
  • 2010-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-12
  • 2011-11-21
  • 1970-01-01
相关资源
最近更新 更多