Question

Update: I modified the example so that can be compiled and tested.

I have an implicit class that defines an enrichment method:

case class Pipe[-I,+O,+R](f: I => (O, R));

object Pipe {
  // The problematic implicit class:
  implicit class PipeEnrich[I,O,R](val pipe: Pipe[I,O,R]) extends AnyVal {
    def >->[X](that: Pipe[O,X,R]): Pipe[I,X,R] = Pipe.fuse(pipe, that);
    def <-<[X](that: Pipe[X,I,R]): Pipe[X,O,R] = Pipe.fuse(that, pipe);
  }

  def fuse[I,O,X,R](i: Pipe[I,O,R], o: Pipe[O,X,R]): Pipe[I,X,R] = null;

  // Example that works:
  val p1: Pipe[Int,Int,String] = Pipe((x: Int) => (x, ""));
  val q1: Pipe[Int,Int,String] = p1 >-> p1;

  // Example that does not, just because R = Nothing:
  val p2: Pipe[Int,Int,Nothing] = Pipe((x: Int) => (x, throw new Exception));
  val q2: Pipe[Int,Int,String] = p2 >-> p2;
}

The problem is it doesn't work when R is Nothing in the second example. It results in an compiler error: In such a case, I get the following compiler error:

Pipe.scala:19: error: type mismatch;
 found   : Pipe[Int,Int,R]
 required: Pipe[Int,Int,String]
  val q2: Pipe[Int,Int,String] = p2 >-> p2;

Why does this happen?


I managed to solve it by creating a separate implicit class for that case:

trait Fuse[I,O,R] extends Any {
  def >->[X](that: Pipe[O,X,R])(implicit finalizer: Finalizer): Pipe[I,X,R];
}

protected trait FuseImpl[I,O,R] extends Any with Fuse[I,O,R] {
  def pipe: Pipe[I,O,R];
  def >->[X](that: Pipe[O,X,R]) = Pipe.fuse(pipe, that);
  def <-<[X](that: Pipe[X,I,R]) = Pipe.fuse(that, pipe);
}

implicit class PipeEnrich[I,O,R](val pipe: Pipe[I,O,R])
  extends AnyVal with FuseImpl[I,O,R];
implicit class PipeEnrichNothing[I,O](val pipe: Pipe[I,O,Nothing])
  extends AnyVal with FuseImpl[I,O,Nothing];

But can I rely on Scala's behavior in the future, that it will not consider Nothing as an option for R? If that changes in the future, the code will stop working because I'll have two different applicable implicits.

Was it helpful?

Solution

Well... You haven't shown all your code, and the code you did show has some confusing inconsistencies. So this is going to be a wild guess. I suspect your problem is that Pipe is invariant in its type parameter R. Here's my simplified example:

case class Test[A](a: A)

object Test {
  implicit class TestOps[A](val lhs: Test[A]) extends AnyVal {
    def >->(rhs: Test[A]): Test[A] = ???
  }

  def test {
    def lhs = Test(???)
    def rhs = Test(???)
    lhs >-> rhs
  }
}

The compile error I get from this code is:

value >-> is not a member of Test[Nothing]
     lhs >-> rhs
         ^

... which I admit is not the same as the error you posted. But I don't entirely trust what you posted, so I'm going to keep going! The fix for this is to make Test covariant in its type parameter A:

case class Test[+A](a: A)

I honestly don't understand why the compile error happens to begin with. It seems like the compiler doesn't want to unify A =:= Nothing in the conversion to TestOps, but I don't see why not. Nevertheless, Test should be covariant in A anyways, and I'm guessing your Pipe class should likewise be covariant in R.

Edit

I just spent a few minutes looking through the Scala bug list and found several possibly related issues: SI-1570, SI-4509, SI-4982 and SI-5505. I don't really know any details, but it sounds like Nothing is treated specially and shouldn't be. Paul and Adriaan would be the guys to ask...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top