Let's say we have a class that has a covariant and a contravariant type parameter:
sealed trait Pipe[-I,+O,+R]
// case subclasses
And we have monadic operations defined for instances of this class:
object Pipe {
def flatMap[I,O,Ri,R](p: Pipe[I,O,Ri], f: Ri => Pipe[I,O,R]): Pipe[I,O,R] =
...
}
To be able to use for
-comprehension, we need that flatMap
is a method of the trait itself:
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);
}
However, this does not compile, it fails with
contravariant type
I
occurs in covariant position in type(R) => Pipe[I,O,R1]
of valuef
.
(A similar error occurs for covariant type parameters as well.)
I understand the restriction and why the problem occurs. But is there some workaround, how to define flatMap
on the trait using Pipes.flatMap
with the same semantics as above? Perhaps using some implicit conversions and/or an intermediate builder class?
Most simply,
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)
}
If your Pipe
permits implementing point
, i.e. def point[I, O, A](a: A): Pipe[I, O, A]
, then implementing a full scalaz.Monad
typeclass could be useful, as Scalaz's implicits would give you flatMap
and many other monadic operations for free:
implicit def pipeMonad[I, O] = new Monad[({type λ[α]=Pipe[I, O, α]})#λ] {
// TODO implement point and bind
}