1

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
Frederick Roth
  • 2,748
  • 4
  • 28
  • 42

2 Answers2

1

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)

Yaneeve
  • 4,751
  • 10
  • 49
  • 87
1

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].

Sebastian Celestino
  • 1,388
  • 8
  • 15
  • Nice, that explains it, however, when I add a JsonWriter[Nothing] due to contravariance my code can't decide anylonger whether to take JsonWriter[String] or JsonWriter[Nothing]. Any ideas? – Frederick Roth Aug 09 '18 at 07:51
  • 1
    I'd been trying diff approaches, as you say, Nothing and contravariance is a problem. The only way a could get it work is giving a type to None. (eg None:Option[String]) – Sebastian Celestino Aug 09 '18 at 15:24