In Scala I can enforce type equality at compile time. For example:
case class Foo[A,B]( a: A, b: B )( implicit ev: A =:= B )
scala> Foo( 1, 2 )
res3: Foo[Int,Int] = Foo(1,2)
scala> Foo( 1, "2" )
<console>:10: error: Cannot prove that Int =:= java.lang.String.
Is there a way to enforce that type A and type B should be different ?
How about something like this, then?
Then:
The idea is to make resolution ambiguous when
A
is the same asB
, and unambiguous when they are not the same. To further emphasize that the ambiguous methods should not be called, I added an implicit of typeNothing
, which should never be around (and should certainly look wrong to the caller if they try to insert one explicitly). (The role of theDummyImplicit
is just to give a different signature to the first two methods.)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:
Attemtping to do
f[String]
will fail to compile with: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:Now let's try to call
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):
Then we can define
f
like this: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 ofB
:And for expressing that
A
is not convertible toB
:Riffing off of Jean-Philippe's ideas, this works:
Then:
I'd probably simplify this as follows, since the checks for "cheating" can always be circumvented anyway (e.g.
Foo(1, 1)(null)
or=!=.nequal(null)
):Here's another attempt:
Then, again:
Like in my other proposal, the aim here is to introduce a compile-time ambiguity when
A
andB
are the same. Here, we provide two implicits for the case whereA
is the same asB
, and an unambiguous implicit when this is not the case.Note that the problem is that you could still explicitly provide the implicit parameter by manually calling
=!=.notMeantToBeCalled1
or=!=.unambigouslyDifferent
. I couldn't think of a way to prevent this at compile time. However, we can throw an exception at runtime, with the trick thatunambigouslyDifferent
requires an evidence parameter itself indicating whetherA
is the same asB
. But wait... Aren't we trying to prove the exact opposite? Yes, and that's why thatsame
implicit parameter has a default value ofnull
. And we expect it to benull
for all legal uses — the only time where it would not benull
is when a nasty user calls e.g.Foo(1f, 1f)(=:=.unambiguouslyDifferent[Float, Float])
, and there we can prevent this cheating by throwing an exception.Based on Landei's idea, the following seems to work:
I have a simpler solution, which also leverages ambiguity,
The original use case,
Update
We can relate this to my "magical typesystem tricks" (thanks @jpp ;-) as follows,
Sample REPL session,