简体   繁体   中英

Generic Derivation of Typeclass from shapeless-guide

Given the following from the excellent shapeless-guide :

package net

import shapeless.labelled.FieldType
import shapeless._

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

The book shows how to write a generic JsonEncoder , ie given an A , provide a JSON representation of that A :

trait JsonEncoder[A] {
  def encode(value: A): JsonValue
}
object JsonEncoder {

  def apply[A](implicit ev: JsonEncoder[A]): JsonEncoder[A] =
    ev

  def instance[A](f: A => JsonValue): JsonEncoder[A] =
    new JsonEncoder[A] {
      override def encode(x: A): JsonValue = f(x)
    }

  implicit val doubleEncoder: JsonEncoder[Double] =
    instance[Double](JsonNumber)

  implicit val stringEncoder: JsonEncoder[String] =
    instance[String](JsonString)

  implicit val boolEncoder: JsonEncoder[Boolean] =
    instance[Boolean](JsonBoolean)

  implicit def optionEncoder[A](implicit ev: JsonEncoder[A]): JsonEncoder[Option[A]] =
    instance[Option[A]] {
      case Some(a) => ev.encode(a)
      case None    => JsonNull
    }

  implicit def listEncoder[A](implicit ev: JsonEncoder[A]): JsonEncoder[List[A]] =
    instance[List[A]]( list => JsonArray(list.map(ev.encode)))

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

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

  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] =
    createObjectEncoder { hlist =>
      val fieldName = witness.value.name
      val head      = hEncoder.value.encode(hlist.head)
      val tail      = tEncoder.encode(hlist.tail)
      JsonObject( (fieldName, head) :: tail.fields )
  }

  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) )
    }

As I understand, the above code will generically derive a case class 's JsonEncoder . However, it will not handle a sealed trait or Coproduct:

import net.JsonEncoder

case class A(name: String, age: Double)

scala> JsonEncoder[A]
res0: net.JsonEncoder[A] = net.JsonEncoder$$anon$1@6b974139

sealed trait Parent
case class Kid() extends Parent

scala> JsonEncoder[Parent]
<console>:14: error: could not find implicit value for parameter ev: net.JsonEncoder[Parent]
       JsonEncoder[Parent]
                  ^

import shapeless._

type Co = Kid :+: CNil

scala> JsonEncoder[Co]
<console>:20: error: could not find implicit value for parameter ev: net.JsonEncoder[Co]
       JsonEncoder[Co]
                  ^

Then, the text provides the following code to, as I understand, generically derive a JsonEncoder instance for a JsonEncoder :

 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)
    }
  }

However, from the REPL, I still was unable to derive a sealed trait or Coproduct:

import net.JsonEncoder

scala> sealed trait Parent
defined trait Parent
case class Kid() extends Parent

scala> JsonEncoder[Parent]
<console>:14: error: could not find implicit value for parameter ev: net.JsonEncoder[Parent]
       JsonEncoder[Parent]
                  ^

import shapeless._

type Co = Kid :+: CNil

scala> JsonEncoder[Co]
<console>:17: error: could not find implicit value for parameter ev: net.JsonEncoder[Co]
       JsonEncoder[Co]
                  ^

Why were those two instances, ie Parent and Co , not derived?

You should remove the upper bound on type parameter H of genericObjectEncoder . If you restrict it to be a subtype of HList , you exclude the cases where it is actually a Coproduct .

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

Now you can see that it works.

scala> sealed trait Parent; case class Kid() extends Parent
defined trait Parent
defined class Kid

scala> JsonEncoder[Parent]
res0: JsonEncoder[Parent] = JsonEncoder$$anon$1@127aef0a

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM