3
votes

I'm trying to implement an implicit Not[T] in Scala, and as such I want to throw an compiler error when there is an implicit val in scope of type T. I thought of doing this using ambiguous implicits as the Scaladoc shows a way to implement =!=. (See below)

But, I don't understand why two newAmbigs are necessary, because there still seems to be an ambiguous implicit if I remove one, as there originally seems to be 3 viable implicits. (See below)

I could not find any documentation on what is required for the compiler to flag an ambiguous implicit.

The implementation of =!= shown by the Scaladoc:

trait =!=[C, D]

implicit def neq[E, F] : E =!= F = null

@annotation.implicitAmbiguous("Could not prove ${J} =!= ${J}")
implicit def neqAmbig1[G, H, J] : J =!= J = null
implicit def neqAmbig2[I] : I =!= I = null

implicitly[Int =!= Int]

The implementation of =!= that seems to work but does not:

trait =!=[C, D]

implicit def neq[E, F] : E =!= F = null

@annotation.implicitAmbiguous("Could not prove ${J} =!= ${J}")
implicit def neqAmbig1[G, H, J] : J =!= J = null

implicitly[Int =!= Int]

As both the neqAmbig1 and neq should be of the same type, and both should be found.

But, this doesn't throw a compiler error, and when tested, just returns null.

2

2 Answers

2
votes

Let's start by simplifying and clarifying the situation.

This works. The implicit is resolved as expected.

trait =!=[C, D]

implicit def neq[E, F] : E =!= F =
  new =!=[E,F]{override def toString="NEQ"}

implicitly[Int =!= Int]  //res0: Int =!= Int = NEQ

Now let's add what should be an ambiguous implicit.

trait =!=[C, D]

implicit def neq[E, F] : E =!= F =
  new =!=[E,F]{override def toString="NEQ"}

implicit def neqAmbig1[E, F] : E =!= E =
  new =!=[E,E]{override def toString="NeqAm1"}

implicitly[Int =!= Int]  //res0: Int =!= Int = NeqAm1

Hmmm. In the case of Int =!= Int we have E as Int, and F as Int, so type E =!= F should be the same as E =!= E, and yet the compiler does not consider them to be equivalent and chooses the E =!= E version (no matter what order the implicits are defined in the code).

I believe what's going on here is described in the language spec:

If there are several eligible arguments which match the implicit parameter's type, a most specific one will be chosen using the rules of static overloading resolution.

This is from the section on Implicit Parameters but I think it still applies: type E =!= E is more specific to Int =!= Int than type E =!= F is.

2
votes

isAsSpecific seems to be the relevant part of the compiler that codifies overloading resolution rules from the spec, so see if you can decipher those:

/** Is type `ftpe1` strictly more specific than type `ftpe2`
 *  when both are alternatives in an overloaded function?
 *  @see SLS (sec:overloading-resolution)
 */
 def isAsSpecific(ftpe1: Type, ftpe2: Type): Boolean

To confirm your first implementation is correct consider Type inequality as provided by shapeless

// Type inequalities
trait =:!=[A, B] extends Serializable
implicit def neq[A, B] : A =:!= B = new =:!=[A, B] {}
implicit def neqAmbig1[A] : A =:!= A = unexpected
implicit def neqAmbig2[A] : A =:!= A = unexpected

For example,

import shapeless.{<:!<, =:!=}

def foo[A](a: A)(implicit ev: A =:!= String): A = a

foo(3)  // ok
foo("") // error: ambiguous implicit values