0

The following code does not compile on Scala 2.12 / 2.13. Why?

class X[U, T]

object X {
  implicit def genericX[U, T](implicit ev: T <:< U): X[U, T] = new X[U, T]
}

implicitly[X[AnyRef, String]]  // compiles
implicitly[X[String, Nothing]] // does not compile
Kamil Kloch
  • 333
  • 1
  • 9
  • https://stackoverflow.com/questions/15310451/why-doesnt-scalas-implicit-class-work-when-one-of-the-type-parameters-should-b – Dmytro Mitin May 22 '20 at 09:36
  • Thanks @dmytro-mitin, I know that `+T` solves the problem, but unfortunately this is not what I can accept there. In fact, I would like to have `T` contravariant, but got stuck even in the invariant case. – Kamil Kloch May 22 '20 at 10:00
  • Why do you need `X`? Now it's basically a synonym for `<:<`. Why can't you use `<:<` directly? Why do you need implicit to be resolved for `Nothing`? How does `Nothing` appear in your code? – Dmytro Mitin May 22 '20 at 10:06
  • `<:<` seems to resolve for `Nothing`: `implicitly[Nothing <:< String]` compiles. Any why can't `X` be covariant with respect to `T`? Brief answer to your question "why" is because compiler doesn't like to infer `Nothing` while resolving implicits. It's a known behavior. In that SO question above there are links to issues at Scala bug tracker. If your qustion is actually "how to fix" rather than "why" then you should provide more details. Currently it sounds like [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Dmytro Mitin May 22 '20 at 10:59
  • 1
    It is a bit complex and relates to https://stackoverflow.com/questions/61890200/implicit-resolution-fails-for-a-contravariant-type-with-a-type-bound. Ultimately I would like to have a method `def check[T](....): A[T] = ???` so that `check(...)` returns `A[Any]` while `check[String](...)` returns `A[String]`. (i.e. `Any` inferred as a default type, instead of `Nothing`). I tried the following: `def check[T](implicit ev: X[Any, T]): A[T] = ???` and hence `X[U, -T].` – Kamil Kloch May 22 '20 at 11:04
  • You didn't answer why you need `X` while you have standard `<:<`. Actually it's contravariant with respect to `T` as you wanted. You can just introduce type alias `type X[U, T] = T <:< U`. Do you have some practical question or are just playing with implicits? By the way in Dotty your code compiles https://scastie.scala-lang.org/ygACfoh9QGyAHlYAMrOhig If you still have a question I guess you should reformulate your question. – Dmytro Mitin May 22 '20 at 11:39
  • Or `type X[U, -T] = T <:< U`. – Dmytro Mitin May 22 '20 at 11:46
  • What is `A` in your comment? – Dmytro Mitin May 22 '20 at 13:34
  • 1
    Thanks for the feedback! Let me paint the whole picture: `A[T]` captures `ClassTag[T]` inside. I am looking for a method `def attribute[T](s: String): A[T]`, which returns `A[SomeDefaultType]` when `T` is missing (aor even `Any`, to make things simpler). I do have a working solution (https://gist.github.com/kamilkloch/a5c97d0c7cdec47f8dc4c4ac4c131674), however, it requires an intermediate class to curry type parameters. I was hoping to get rid of it with the aforementioned `X[U, -T]`. – Kamil Kloch May 22 '20 at 21:48
  • in new [question](https://stackoverflow.com/questions/62003214/implicit-error-when-trying-to-implement-the-absurd-typeclass) [workaround](https://www.reddit.com/r/scala/comments/73791p/nothings_twin_brother_the_better_one/) was reminded: `type Bottom <: Nothing`. Unfortunately this seems not to help in your use case with `attribute` since if type parameter is not specified it's inferred to be `Nothing` anyway and not `Bottom`. – Dmytro Mitin May 26 '20 at 00:43

1 Answers1

2

Long story short, compiler doesn't like to infer Nothing in implicits.

Why doesn't Scala's implicit class work when one of the type parameters should be Nothing?

Implicit error when trying to implement the `Absurd` typeclass

https://www.reddit.com/r/scala/comments/73791p/nothings_twin_brother_the_better_one/

http://guillaume.martres.me/talks/typelevel-summit-oslo/?fbclid=IwAR1yDSz-MetOgBh0uWMeuBuuL6wlD79fN_4NrxAtl3c46JB0fYCYeeGgp1Y#/9 (slide 10 "The fear of Nothing")

https://www.youtube.com/watch?v=YIQjfCKDR5A?t=459 (7:39)

https://www.youtube.com/watch?v=lMvOykNQ4zs

The fear of Nothing

  • scalac instantiates eagerly, even when it isn't safe

  • Counterbalanced by never inferring Nothing

    class Foo[T] { def put(x: T) = {} }
    (new Foo).put("") // T? = String
    
  • Fails if the lower bound isn't Nothing

    class Foo[T >: Null] { def put(x: T) = {} }
    (new Foo).put("") // T? = Null
    // type mismatch: Null does not match String
    
  • Sometimes you really want to infer Nothing!

    class Foo[T]
    def foo[T](x: Foo[T]) = x
    foo(new Foo[Nothing]) // error
    

Workaround is to introduce type Bottom

type Bottom <: Nothing

implicitly[Bottom =:= Nothing]
implicitly[Nothing =:= Bottom]

implicitly[X[AnyRef, String]]  // compiles
// implicitly[X[String, Nothing]] // does not compile
implicitly[X[String, Bottom]] // compiles
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Unfortunately, `implicitly[ClassTag[Bottom]]` does not work... – Kamil Kloch Jun 05 '20 at 21:33
  • @KamilKloch Try to add `implicit val bottomClassTag: ClassTag[Bottom] = new ClassTag[Bottom] { override def runtimeClass: Class[_] = implicitly[ClassTag[Nothing]].runtimeClass }`. – Dmytro Mitin Jun 05 '20 at 21:39
  • Thanks, it works even more straightforward. But ultimately, `Bottom` trick does not help with type inference for `X[U, -T]`, please take a look: https://gist.github.com/kamilkloch/d4950417ee420fc3fc2a31089b918ef4 – Kamil Kloch Jun 05 '20 at 21:59