In scala 2.13, how to use implicitly[value singleton type]?
Asked Answered
D

1

1

The following simple code:

  implicit val a: String = "abc"

  implicitly[a.type]

fails to compile despite that a is totally in the scope and consistent in type:

Error:(9, 13) could not find implicit value for parameter e: 
...AP.a.type with Singleton
  implicitly[a.type with Singleton]

It appears that this inconsistent behaviour is deliberate. What's the point of this design? What is the shortest change I can do to make it compile?

UPDATE 1: I just realised that the annotation "String" is the culprit, the following code total worked:

  val a: String = "abc"

  implicit val aa: a.type = a

  implicitly[a.type]

Unfortunately it contains a lot of duplicated definition, is there any chance to make it shorter?

Thanks a lot for your help.

Derisive answered 6/7, 2020 at 0:34 Comment(3)
I guess your real use case is a bit different because I really do not see any point in making this snippet compile. Maybe if you can share more of your real use case we can come to better alternatives.Spector
@Derisive Why did you tag "path-dependent-type" and "abstract-type"? There are no path-dependent or abstract types in your question.Sultan
Instead of declaring the type as a.type, you can use a type alias to make it consistent across like type at = String; implicit val a: at = ...; implicitly[at]Estuary
S
4

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.

Sultan answered 6/7, 2020 at 2:4 Comment(8)
Even if you declare val a: String, isn't a still of type a.type? The behavior here makes sense: the implicit search appears to be looking at the declared type of the implicits (and a is declared String), but I don't think that's justified by the LSP or anything, since the implicits actually have, statically, a more refined type than their declarations. Or: with implicit val a: Parent = Child(), a is not statically known to be Child, so implicitly[Child] fails. But a is definitely known to be a.type, so why should implicitly[a.type] fail?Paving
@Paving "since the implicits actually have, statically, a more refined type than their declarations." Why do you think so?Sultan
Because a: a.type compiles? The expression a conforms to type a.type, even though the variable a was only declared String.Paving
@Paving how is that relevant to implicits? ("since the implicits actually have, statically, a more refined type than their declarations.")Sultan
Because implicit search is type directed. implicitly[a.type] says "look for an implicit val/implicit def that has type/returns a.type". And look, I have implicit val a: String, and a has type a.type, so why shouldn't I get it? Again, sure, you can say "that's just how it is" and say that since a is declared String that's what matters, never mind that a actually does have type a.type. But you can't justify that with the LSP or similar, because the LSP points out situations that are fundamentally wrong, and there's nothing wrong here. It's just an arbitrary choice.Paving
@Paving Maybe you're right since manually resolved implicit val a: String = null; implicitly[a.type](a) compiles. So we just should say that compiler doesn't like to summon singletons.Sultan
Final note, just tested it: dotty's fine with it. Just a scalac/Scala 2 idiosyncrasy, then.Paving
@Paving By the way, when you said "since the implicits actually have, statically, a more refined type than their declarations" I thought you were talking about situation trait TC[In] { type Out }; def foo[In](implicit tc: TC[In]): TC[In] { type Out = tc.Out } = tc (that's why I was asking) but this is not relevant just to implicits, it's just how type refinement works in Scala: def foo[In](tc: TC[In]): TC[In] { type Out = tc.Out } = tc and type In; val x: TC[In] = ???; val y: TC[In] { type Out = x.Out } = x compile too.Sultan

© 2022 - 2024 — McMap. All rights reserved.