66

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 ?

paradigmatic
  • 40,153
  • 18
  • 88
  • 147
  • 2
    Nice one… But does it have useful applications? Sounds like something like [Miles Sabin's magical typesystem tricks](http://www.chuusai.com/2011/06/09/scala-union-types-curry-howard/) could maybe be of use. – Jean-Philippe Pellet Aug 02 '11 at 08:47
  • @Jean-Phillipe_Pellet: I would like to make a bidirectional map of types [A,B], when I call apply with A, it returns a B, and when I call apply with B it returns an A. I don't know if it will be possible, but at least I need to different types. – paradigmatic Aug 02 '11 at 08:49
  • I see. Mightn't you want to name the two accessor methods differently then, to allow for the same type parameters? – Jean-Philippe Pellet Aug 02 '11 at 08:52
  • That would be plan B, if I cannot find a way to implement the fist idea. – paradigmatic Aug 02 '11 at 08:55
  • 7
    Interesting. I thought that something along the lines of `implicit def t[N, A >: N, B >: N]()(implicit n: N =:= Nothing) = new =/=[A,B]` would work (expressing "if the closest common subtype of A and B is Nothing, they are different"), but it doesn't... – Landei Aug 02 '11 at 09:44

7 Answers7

57

I have a simpler solution, which also leverages ambiguity,

trait =!=[A, B]

implicit def neq[A, B] : A =!= B = null

// This pair excludes the A =:= B case
implicit def neqAmbig1[A] : A =!= A = null
implicit def neqAmbig2[A] : A =!= A = null

The original use case,

case class Foo[A,B](a : A, b : B)(implicit ev: A =!= B)
new Foo(1, "1")
new Foo("foo", Some("foo"))

// These don't compile
// new Foo(1, 1)
// new Foo("foo", "foo")
// new Foo(Some("foo"), Some("foo"))

Update

We can relate this to my "magical typesystem tricks" (thanks @jpp ;-) as follows,

type ¬[T] = T => Nothing
implicit def neg[T, U](t : T)(implicit ev : T =!= U) : ¬[U] = null

def notString[T <% ¬[String]](t : T) = t

Sample REPL session,

scala> val ns1 = notString(1)
ns1: Int = 1

scala> val ns2 = notString(1.0)
ns2: Double = 1.0

scala> val ns3 = notString(Some("foo"))
ns3: Some[java.lang.String] = Some(foo)

scala> val ns4 = notString("foo")
<console>:14: error: No implicit view available from 
  java.lang.String => (String) => Nothing.
       val ns4 = notString2("foo")
                            ^
Miles Sabin
  • 23,015
  • 6
  • 61
  • 95
  • 1
    Nice! It's probably worth pointing out that, as with the other answers, this implements negation-as-failure: `notString("foo": AnyRef)` compiles. – Aaron Novstrup Aug 17 '11 at 17:51
  • 1
    @Aaron this technique, and indeed the question, only makes sense in a static context. Otherwise there'd be no other option than to do dynamic runtime type tests. – Miles Sabin Aug 18 '11 at 14:16
  • @Miles I wondered if there was a way to statically enforce a stronger notion of inequality, where the static constraint indicates that any two instances of the types have different runtime types. For example, String and Int are unequal in this sense, while String and AnyRef are not. `String with Int` would seem to be a contradiction, but, if my understanding is correct, it's not instantiable. – Aaron Novstrup Aug 18 '11 at 14:49
  • 1
    @Aaron not entirely sure what you mean, but relatedly it's possible to define a type operator A <:!< B which states that A is not a subtype of B (ie. it's the opposite of A <:< B from predef). – Miles Sabin Aug 19 '11 at 10:02
  • 2
    @Miles Suppose I want to define `=!=` so that `A =!= B` if and only if every non-null instance of `A` is not an instance of `B` and every non-null instance of `B` is not an instance of `A`. By this definition, `String =!= Int`. My intuition is that the compiler has the information to enforce the constraint, but that it's not expressible in Scala. – Aaron Novstrup Aug 19 '11 at 20:49
  • Note that `A =!= B` is not equivalent to `A <:!< B AND B <:!< A`. Consider definitions `class A`, `class B`, and `trait T`. Then `A =!= B`, but *not* `A =!= T` since `new A with T` is an instance of both `A` and `T`. – Aaron Novstrup Aug 19 '11 at 20:53
  • this solution works for `A =!= Nothing` while @AaronNovstrup's doesn't. – mikea Dec 19 '13 at 20:16
  • @mikea Actually, mine does (at least in 2.9.1, 2.10.0, and 2.10.3). – Aaron Novstrup Dec 20 '13 at 18:37
  • Nice solution but can we get a better error, such as `Types (A, B) do not confirm to A =!= B]`? – Jus12 Nov 13 '17 at 15:16
29

Riffing off of Jean-Philippe's ideas, this works:

sealed class =!=[A,B]

trait LowerPriorityImplicits {
  implicit def equal[A]: =!=[A, A] = sys.error("should not be called")
}
object =!= extends LowerPriorityImplicits {
  implicit def nequal[A,B](implicit same: A =:= B = null): =!=[A,B] = 
    if (same != null) sys.error("should not be called explicitly with same type")
    else new =!=[A,B]
}     

case class Foo[A,B](a: A, b: B)(implicit e: A =!= B)

Then:

// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))

// doesn't compile
// Foo(1f, 1f)
// Foo("", "")

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)):

sealed class =!=[A,B]

trait LowerPriorityImplicits {
  /** do not call explicitly! */
  implicit def equal[A]: =!=[A, A] = sys.error("should not be called")
}
object =!= extends LowerPriorityImplicits {
  /** do not call explicitly! */
  implicit def nequal[A,B]: =!=[A,B] = new =!=[A,B]
}
Aaron Novstrup
  • 20,967
  • 7
  • 70
  • 108
  • Thanks! I've found something similar in a blog comment, but unfortunately, the check happens at runtime not at compilation. – paradigmatic Aug 03 '11 at 16:48
  • @paradigmatic I'm not sure whether I'm interpreting your comment correctly.... In my code the check happens at compile-time -- did you mean that the blog comment demonstrates a runtime check? – Aaron Novstrup Aug 03 '11 at 17:11
  • My bad. Your solution works perfectly ! I've understood the difference with what I've seen. I checked it with subtypes and it also works well. – paradigmatic Aug 03 '11 at 17:23
  • 1
    @paradigmatic Updated with a simpler version that eliminates the "cheating" checks. As explained in the answer, those checks can always be circumvented anyway. – Aaron Novstrup Aug 03 '11 at 17:45
  • 1
    @Aaron Nice simplification. You could declare `sealed class =!=[A,B] extends NotNull` to prevent nasty users from manually passing `null`. Similarly, we could have a `NotNull` subclass of `=:=` to enforce the cheating check in your first version. The last thing we cannot prevent is a manual call to `equal`… – Jean-Philippe Pellet Aug 04 '11 at 08:18
  • Hmm… Forget about the `NotNull` subclass of `=:=`… – Jean-Philippe Pellet Aug 04 '11 at 08:24
  • I am accepting this answer as it is the simplest one. I don't really care about making "idiot"-proof, because I fear idiots will always find a solution to circumvent it... – paradigmatic Aug 04 '11 at 09:34
  • Unfortunately this one doesn't work with the use case `def foo[T](implicit ev: T =!= Nothing): T` – sourcedelica Sep 15 '12 at 14:20
  • @sourcedelica works for me in Scala 2.9.1. `foo` (with no type argument) results in a compile error, while `foo[String]` compiles and runs without incident. – Aaron Novstrup Sep 16 '12 at 00:46
  • @sourcedelica Although curiously it doesn't seem to work if the parameter is already bound as in `class C[T](v: T) { def foo[T](implicit ev: T =!= Nothing) = v }`. In this example, `new C("a").foo[String]` compiles but `new C("a").foo` does not. – Aaron Novstrup Sep 16 '12 at 00:54
  • @sourcedelica Nevermind, that was just me being dumb. `class C[T](v: T) { def foo(implicit ev: T =!= Nothing) = v }` works just fine. – Aaron Novstrup Sep 16 '12 at 00:56
  • @AaronNovstrup `def foo[A](implicit e: A =!= Nothing) = ???` doesn't seem to work in scala 2.10.3 – mikea Dec 19 '13 at 20:13
  • @mikea Not sure what you mean. With that definition, calling `foo[Nothing]` or just `foo` results in a compile-time error (as desired), and calling `foo[String]` results in a run-time error (because you've provided no method body). That's exactly what it's supposed to do. – Aaron Novstrup Dec 20 '13 at 18:35
19

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 }
Régis Jean-Gilles
  • 32,541
  • 5
  • 83
  • 97
  • This is the best answer! Just got this working on Scala 2.12 in 2021. – VolatileRig Dec 06 '21 at 23:04
  • I was trying to prevent Futures from being passed into my timing method, here's the signature: `def time[T:IsNotSub[Future[Any]]#λ](reporting: Double => Unit, block: => T)` – VolatileRig Dec 06 '21 at 23:10
  • Nice. Though arguably in this case you might want to just handle the future as a special case instead of outright forbidding futures to be passed (with a separate overload) and take the end time once the future completes. – Régis Jean-Gilles Dec 07 '21 at 09:49
9

Based on Landei's idea, the following seems to work:

case class Foo[A, B <: A, C <: A]( a: B, b: C)(implicit f: AnyVal <:< A)

scala> Foo(1f, 1.0)
res75: Foo[AnyVal,Float,Double] = Foo(1.0,1.0)

scala> Foo("", 1.0)
res76: Foo[Any,java.lang.String,Double] = Foo(,1.0)

scala> Foo(1f, 1f)
<console>:10: error: Cannot prove that AnyVal <:< Float.
       Foo(1f, 1f)
          ^

scala> Foo("", "")
<console>:10: error: Cannot prove that AnyVal <:< java.lang.String.
       Foo("", "")
          ^

scala> Foo("", 1)
res79: Foo[Any,java.lang.String,Int] = Foo(,1)
Community
  • 1
  • 1
Vasil Remeniuk
  • 20,519
  • 6
  • 71
  • 81
  • Nice trick! But this only works for primitive types, and only because the hierarchy is flat there. This does not work: `Foo("Fish",Some("Fish"))`. – Rex Kerr Aug 02 '11 at 16:39
6

Here's another attempt:

class =!=[A, B] private () extends NotNull

object =!= {
  implicit def notMeantToBeCalled1[A, B >: A, C >: B <: A]: =!=[B, A] = error("should not be called")
  implicit def notMeantToBeCalled2[A, B >: A, C >: B <: A]: =!=[B, A] = error("should not be called")
  implicit def unambigouslyDifferent[A, B](implicit same: A =:= B = null): =!=[A, B] =
    if (same != null) error("should not be called explicitly with the same type")
    else new =!=
}

case class Foo[A, B](a: A, b: B)(implicit ev: A =!= B)

Then, again:

// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))

// doesn't compile
// Foo(1f, 1f)
// Foo("", "")

Like in my other proposal, the aim here is to introduce a compile-time ambiguity when A and B are the same. Here, we provide two implicits for the case where A is the same as B, 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 that unambigouslyDifferent requires an evidence parameter itself indicating whether A is the same as B. But wait... Aren't we trying to prove the exact opposite? Yes, and that's why that same implicit parameter has a default value of null. And we expect it to be null for all legal uses — the only time where it would not be null 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.

Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234
  • I'm not sure the cheating checks are worth the additional complexity -- especially since the nasty user can still call `=!=.unambiguoslyDifferent[Float, Float](null)`, or, since `=!=` is just a phantom type, `Foo(1, 1)(null)`. It *would* be nice if Scala had some way to express that an implicit method can not be called explicitly. – Aaron Novstrup Aug 03 '11 at 17:38
2

How about something like this, then?

class Foo[A, B] private (a: A, b: B)

object Foo {
  def apply[A, B <: A, C >: A <: B](a: A, b: B)(implicit nothing: Nothing) = nothing
  def apply[A, B >: A, C >: B <: A](a: A, b: B)(implicit nothing: Nothing, dummy: DummyImplicit) = nothing
  def apply[A, B](a: A, b: B): Foo[A, B] = new Foo(a, b)
}

Then:

// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))

// doesn't compile
// Foo(1f, 1f)
// Foo("", "")

The idea is to make resolution ambiguous when Ais the same as B, and unambiguous when they are not the same. To further emphasize that the ambiguous methods should not be called, I added an implicit of type Nothing, which should never be around (and should certainly look wrong to the caller if they try to insert one explicitly). (The role of the DummyImplicit is just to give a different signature to the first two methods.)

Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234
1

This is not an answer, just the beginnings of what I could think is an answer. The code below will return either an Yes or an No depending on whether the types are equal or not, if you ask for implicitly[AreEqual[A,B]]. How to go from there to actually making a check I haven't been able to figure out. Maybe the whole approach is doomed, maybe someone can make something out of it. Mind you, implicitly[No[A, B]] will always return something, one can't use that. :-(

class AreEqual[A, B]
trait LowerPriorityImplicits {
  implicit def toNo[A : Manifest, B : Manifest]: No[A, B] = No[A, B]
}
object AreEqual extends LowerPriorityImplicits {
  implicit def toYes[A, B](implicit ev: A =:= B, m1: Manifest[A], m2: Manifest[B]): Yes[A, B] = Yes[A, B]
}

case class Yes[A : Manifest, B : Manifest]() extends AreEqual[A, B] {
  override def toString: String = "Yes(%s, %s)" format (manifest[A].toString, manifest[B].toString)
}
case class No[A : Manifest, B : Manifest]() extends AreEqual[A, B] {
  override def toString: String = "No(%s, %s)" format (manifest[A].toString, manifest[B].toString)
}

Test:

scala> implicitly[AreEqual[String, Option[String]]]
res0: AreEqual[String,Option[String]] = No(java.lang.String, scala.Option[java.lang.String])

scala> implicitly[AreEqual[String, String]]
res1: AreEqual[String,String] = Yes(java.lang.String, java.lang.String)
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681