Why doesn't Scala's implicit class work when one of the type parameters should be Nothing?

蹲街弑〆低调 提交于 2020-07-28 13:54:48

问题


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.


回答1:


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...



来源:https://stackoverflow.com/questions/15310451/why-doesnt-scalas-implicit-class-work-when-one-of-the-type-parameters-should-b

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!