I liked the simplicity and effectiveness of Miles Sabin's first solution, but was a bit dissatisfied with the fact that the error we get is not very helpful:
By example with the following definition:
def f[T]( implicit e: T =!= String ) {}
Attemtping to do f[String]
will fail to compile with:
<console>:10: error: ambiguous implicit values:
both method neqAmbig1 in object =!= of type [A]=> =!=[A,A]
and method neqAmbig2 in object =!= of type [A]=> =!=[A,A]
match expected type =!=[String,String]
f[String]
^
I'd rather have the compiler tell me something along the line of "T is not different from String"
It turns out that it's quite easy if add yet another level of implicits in such a way that we turn the ambiguity error
into an implicit not found error. From then we can use the implicitNotFound
annotation to emit a custom error message:
@annotation.implicitNotFound(msg = "Cannot prove that ${A} =!= ${B}.")
trait =!=[A,B]
object =!= {
class Impl[A, B]
object Impl {
implicit def neq[A, B] : A Impl B = null
implicit def neqAmbig1[A] : A Impl A = null
implicit def neqAmbig2[A] : A Impl A = null
}
implicit def foo[A,B]( implicit e: A Impl B ): A =!= B = null
}
Now let's try to call f[String]
:
scala> f[String]
<console>:10: error: Cannot prove that String =!= String.
f[String]
^
That's better. Thanks compiler.
As a last trick for those that like the context bound syntactic sugar, one can define this alias (based on type lambdas):
type IsNot[A] = { type λ[B] = A =!= B }
Then we can define f
like this:
def f[T:IsNot[String]#λ] {}
Whether it is easier to read is highly subjective. In any case is definitly shorter than writing the full implicit parameter list.
UPDATE: For completeness, here the equivalent code for expressing that A
is is not a sub-type of B
:
@annotation.implicitNotFound(msg = "Cannot prove that ${A} <:!< ${B}.")
trait <:!<[A,B]
object <:!< {
class Impl[A, B]
object Impl {
implicit def nsub[A, B] : A Impl B = null
implicit def nsubAmbig1[A, B>:A] : A Impl B = null
implicit def nsubAmbig2[A, B>:A] : A Impl B = null
}
implicit def foo[A,B]( implicit e: A Impl B ): A <:!< B = null
}
type IsNotSub[B] = { type λ[A] = A <:!< B }
And for expressing that A
is not convertible to B
:
@annotation.implicitNotFound(msg = "Cannot prove that ${A} <%!< ${B}.")
trait <%!<[A,B]
object <%!< {
class Impl[A, B]
object Impl {
implicit def nconv[A, B] : A Impl B = null
implicit def nconvAmbig1[A<%B, B] : A Impl B = null
implicit def nconvAmbig2[A<%B, B] : A Impl B = null
}
implicit def foo[A,B]( implicit e: A Impl B ): A <%!< B = null
}
type IsNotView[B] = { type λ[A] = A <%!< B }