问题
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