如何为具有协变/逆变类型参数的类定义flatMap?

时间:2013-03-03 09:39:16

标签: scala monads covariance contravariance for-comprehension

假设我们有一个具有协变和逆变类型参数的类:

sealed trait Pipe[-I,+O,+R]
// case subclasses

我们为这个类的实例定义了monadic操作:

object Pipe {
    def flatMap[I,O,Ri,R](p: Pipe[I,O,Ri], f: Ri => Pipe[I,O,R]): Pipe[I,O,R] =
        ...
}

为了能够使用for - 理解,我们需要flatMap是特质本身的一种方法:

sealed trait Pipe[-I,+O,+R] {
    def flatMap[I,O,Ri,R](f: Ri => Pipe[I,O,R]): Pipe[I,O,R] =
        Pipe.flatMap(this, f);
}

但是,这不会编译,它会以

失败
  

逆变类型I出现在值(R) => Pipe[I,O,R1]的{​​{1}}类型的协变位置。

(协变类型参数也会发生类似的错误。)

我理解限制以及问题发生的原因。但是有一些解决方法,如何使用f使用与上面相同的语义在特征上定义flatMap?也许使用一些隐式转换和/或中间构建器类?

1 个答案:

答案 0 :(得分:4)

最简单的,

implicit def pipeFlatMap[I, O, A](pipe: Pipe[I, O, A]) = new {
  def flatMap[B](f: A => Pipe[I, O, B]) = Pipe.flatMap(pipe, f)
}

如果您的Pipe允许实施point,即def point[I, O, A](a: A): Pipe[I, O, A],则实施完整的scalaz.Monad类型类可能很有用,因为Scalaz的含义会给您flatMap和许多其他免费的monadic操作:

implicit def pipeMonad[I, O] = new Monad[({type λ[α]=Pipe[I, O, α]})#λ] {
  // TODO implement point and bind
}