Implicit lookup for Typeclass of None is not compatible with Contravariant Typeclass of Option

63 Views Asked by At

I don't get the following code to compile and I am curious of what I did wrong.

I defined a Contravariant Jsonwriter Trait and a function accepting implicit writers:

trait JsonWriter[-A] {
  def write(value: A): Json
}

object Json {
  def toJson[A](value: A)(implicit writer: JsonWriter[A]): Json =
  writer.write(value)
}

Additionally I have defined some instances of these writers:

object JsonWriterInstances {
  implicit val stringWriter: JsonWriter[String] =
    (value: String) => JsString(value)

  implicit val doubleWriter: JsonWriter[Double] =
    (value: Double) => JsNumber(value)

  class OptionWriter[-T](writer: JsonWriter[T]) extends JsonWriter[Option[T]] {
    def write(value: Option[T]): Json = {
        value match {
          case None    => JsNull
          case Some(x) => writer.write(x)
        }
      }
  }
  implicit def optionWriter[T](implicit writer: JsonWriter[T]):
      JsonWriter[Option[T]] = new OptionWriter[T](writer)

}

Now I have written a test:

"write double Option" in {
  Some(1.0).toJson should be(JsNumber(1.0))
  None.toJson should be(JsNull)
}

The first test for Some(1.0) works fine The second one for None throws:

Error:(40, 12) could not find implicit value for parameter writer: JsonWriter[None.type]
    None.toJson should be(JsNull)

If you want to try the code my JsonType definitions for this example are:

sealed trait Json

final case class JsObject(get: Map[String, Json]) extends Json

final case class JsString(get: String) extends Json

final case class JsNumber(get: Double) extends Json

case object JsNull extends Json
2

There are 2 best solutions below

2
Sebastian Celestino On BEST ANSWER

None, if you dont say anything else, is a Option[Nothing], so OptionWriter[Nothing] needs a JsonWriter[Nothing]. If you try Json.toJson(Some(1)) is the same, there is no JsonWriter[Int].

On the other hand, Json.toJson(None:Option[String]) works, because OptionWriter[String] can get a JsonWriter[String].

0
Yaneeve On

I think it has to do with the fact that

case object None extends Option[Nothing] { ... }

if you do one of the following, it will work

toJson(Option.empty[Double])
toJson(None : Option[Double])

Note that the second one uses type ascription to put a face, so to speak, on the Nothing (which is a subtype of everything)