Search code examples
scalatypesshapeless

Cannot find implicit when deriving CoProduct instances using Shapeless.LabelledGeneric


I am learning Shapeless and cannot wrap my head around a compilation error. I am working with the example of deriving CoProduct instances using LabelledGeneric and facing a compilation error. The compilation fails both in the IDE as well as when i run it through the SBT. This is a complete example so one should be able to just copy paste this code and easily see the issue.

import shapeless.{ :+:, ::, CNil, Coproduct, HList, HNil, Inl, Inr, LabelledGeneric, Lazy, Witness }
import shapeless.labelled.FieldType

class DerivingCoProductInstancesWithLabelledGeneric {

  sealed trait JsonValue
  final case class JsonObject(fields: List[(String, JsonValue)]) extends JsonValue
  final case class JsonArray(items: List[JsonValue]) extends JsonValue
  final case class JsonString(value: String) extends JsonValue
  final case class JsonNumber(value: Double) extends JsonValue
  final case class JsonBoolean(value: Boolean) extends JsonValue
  final case object JsonNull extends JsonValue

  sealed trait Shape
  final case class Rectangle(width: Double, height: Double) extends Shape
  final case class Circle(radius: Double) extends Shape

  trait JSONEncoder[A] {
    def encode(value: A): JsonValue
  }
  //Companion object that returns a implicitly available JSONEncoder satisfying the type requirements
  object JSONEncoder {
    def apply[A](implicit encoder: JSONEncoder[A]): JSONEncoder[A] = encoder
  }

  //Make creation of JSON Encoders a little more generic by passing in the function
  def createJSONEncoder[A](func: A => JsonValue): JSONEncoder[A] = new JSONEncoder[A] {
    def encode(value: A) = func(value)
  }

  //Few instances of the type class with primitive types
  implicit val jsonStringEncoder = createJSONEncoder[String](str => JsonString(str))
  implicit val jsonBooleanEncoder = createJSONEncoder[Boolean](bool => JsonBoolean(bool))
  implicit val jsonNumberEncoder = createJSONEncoder[Double](doubleNum => JsonNumber(doubleNum))
  implicit val jsonIntEncoder = createJSONEncoder[Int](intNum => JsonNumber(intNum))

  //Some combinator types
  implicit def listEncoder[A](implicit enc: JSONEncoder[A]): JSONEncoder[List[A]] =
    createJSONEncoder(list => JsonArray(list.map(enc.encode)))

  implicit def optionEncoder[A](implicit enc: JSONEncoder[A]): JSONEncoder[Option[A]] =
    createJSONEncoder(option => option.map(enc.encode).getOrElse(JsonNull))

  //Now to do product derivation we would need to create encoders to represent a HList

  trait JsonObjectEncoder[A] extends JSONEncoder[A] {
    def encode(value: A): JsonObject
  }

  def createObjectEncoder[A](fn: A => JsonObject): JsonObjectEncoder[A] =
    new JsonObjectEncoder[A] {
      def encode(value: A): JsonObject =
        fn(value)
    }

  //Derive defn for HNil and ::
  implicit val hnilEncoder: JsonObjectEncoder[HNil] =
    createObjectEncoder(hnil => JsonObject(Nil))

  implicit def hlistObjectEncoder[K <: Symbol, H, T <: HList](
    implicit
    witness: Witness.Aux[K],
    hEncoder: Lazy[JSONEncoder[H]],
    tEncoder: JsonObjectEncoder[T]
  ): JsonObjectEncoder[FieldType[K, H] :: T] = {
    val fieldName: String = witness.value.name
    createObjectEncoder { hlist =>
      val head = hEncoder.value.encode(hlist.head)
      val tail = tEncoder.encode(hlist.tail)
      JsonObject((fieldName, head) :: tail.fields)
    }
  }

  implicit val cnilObjectEncoder: JsonObjectEncoder[CNil] =
    createObjectEncoder(cnil => throw new Exception("Inconceivable!"))

  implicit def coproductObjectEncoder[K <: Symbol, H, T <: Coproduct](
    implicit
    witness: Witness.Aux[K],
    hEncoder: Lazy[JSONEncoder[H]],
    tEncoder: JsonObjectEncoder[T]
  ): JsonObjectEncoder[FieldType[K, H] :+: T] = {
    val typeName = witness.value.name
    createObjectEncoder {
      case Inl(h) =>
        JsonObject(List(typeName -> hEncoder.value.encode(h)))
      case Inr(t) =>
        tEncoder.encode(t)
    }
  }

  implicit def genericObjectEncoder[A, H <: HList](
    implicit
    generic: LabelledGeneric.Aux[A, H],
    hEncoder: Lazy[JsonObjectEncoder[H]]
  ): JSONEncoder[A] =
    createObjectEncoder { value =>
      hEncoder.value.encode(generic.to(value))
    }

  val shape: Shape = Circle(1.0)
  JSONEncoder[Shape]
}

Solution

  • Remove restriction on type parameter here:

    implicit def genericObjectEncoder[A, H /*<: HList*/](
                                                          implicit
                                                          generic: LabelledGeneric.Aux[A, H],
                                                          hEncoder: Lazy[JsonObjectEncoder[H]]
                                                        ): JSONEncoder[A] =
          createObjectEncoder { value =>
            hEncoder.value.encode(generic.to(value))
          }
    

    Shape is transformed by LabelledGeneric into not an HList but a coproduct.