fails to compile despite that a
is totally in the scope and consistent in type:
It is NOT consistent in type.
Consider example
trait Parent
trait Child extends Parent
{
implicit val p: Parent = null
// implicitly[Child] // doesn't compile
}
{
implicit val c: Child = null
implicitly[Parent] // compiles
}
Similarly in our case a.type <: String
, you declared implicit of type String
, so implicit of type a.type
is not found.
If you have an implicit of some type it will work also for all supertypes but will not work for all subtypes (strictly). This is just Liskov principle. That's why you shouldn't look for implicit of type Any
or define implicit of type Nothing
.
Similarly, if a type class is covariant then all supertypes of an instance of this type class are also its instances
trait TC[+A]
{
implicit val inst: TC[Parent] = null
// implicitly[TC[Child]] // doesn't compile
}
{
implicit val inst: TC[Child] = null
implicitly[TC[Parent]] // compiles
}
If a type class is contravariant then all subtypes of an instance of this type class are also its instances
trait TC1[-A]
{
implicit val inst: TC1[Parent] = null
implicitly[TC1[Child]] // compiles
}
{
implicit val inst: TC1[Child] = null
// implicitly[TC1[Parent]] // doesn't compile
}
Clearly, for invariant type classes there is no such property.
What is the shortest change I can do to make it compile?
It should not compile.
UPDATE 1: I just realised that the annotation "String" is the culprit, the following code total worked
Surely it does. You defined implicit of type a.type
so implicit of this type a.type
is found.
If you are looking for implicits of supertypes you can do
def implicitSupertypeOf[A] = new PartiallyAppliedImplicitSupertypeOf[A]
class PartiallyAppliedImplicitSupertypeOf[A] {
def apply[B]()(implicit b: B, ev: A <:< B): B = b
// by the way, the following will not work:
// def apply[B]()(implicit ev: A <:< B, b: B): B = b
// def apply[B >: A]()(implicit b: B): B = b
}
import Predef.{$conforms => _, _}
{
implicit val p: Parent = null
implicitSupertypeOf[Child]() //compiles
}
{
implicit val inst: TC[Parent] = null
implicitSupertypeOf[TC[Child]]() //compiles
}
{
implicit val inst: TC1[Child] = null
implicitSupertypeOf[TC1[Parent]]() //compiles
}
{
implicit val a: String = "abc"
implicitSupertypeOf[a.type]() //compiles
implicitSupertypeOf["abc"]() //compiles
}
From the above it follows that there is no sense to define implicitSubtypeOf[A]()
because it should behave just like standard implicitly[A]
.
By the way, we can also modify behavior of implicitly
so that it will accept only exact type without subtypes
def implicitExactTypeOf[A] = new PartiallyAppliedImplicitExactTypeOf[A]
class PartiallyAppliedImplicitExactTypeOf[A] {
def apply[B <: A]()(implicit b: B, ev: A =:= B) = b
}
{
implicit val p: Parent = null
// implicitExactTypeOf[Child]() // doesn't compile
implicitExactTypeOf[Parent]() // compiles
}
{
implicit val c: Child = null
implicitExactTypeOf[Child]() // compiles
// implicitExactTypeOf[Parent]() // doesn't compile
}
{
implicit val inst: TC[Parent] = null
// implicitExactTypeOf[TC[Child]]() // doesn't compile
implicitExactTypeOf[TC[Parent]]() //compiles
}
{
implicit val inst: TC1[Child] = null
implicitExactTypeOf[TC1[Child]]() //compiles
// implicitExactTypeOf[TC1[Parent]]() // doesn't compile
}
{
implicit val a: String = "abc"
implicitExactTypeOf[String]() // compiles
// implicitExactTypeOf["abc"]() // doesn't compile
// implicitExactTypeOf[a.type]() // doesn't compile
}
Also we can implement implicitStrictSupertypeOf
(accepting supertypes but not the type itself), implicitStrictSubtypeOf
(like implicitly
accepting subtypes but it will not accept the type itself).
Actually after discussion with @HTNW I guess I understood your point. So we just should say that compiler doesn't like to summon singletons.
a.type
, you can use a type alias to make it consistent across liketype at = String; implicit val a: at = ...; implicitly[at]
– Estuary