2017-01-15 6 views
2

die folgenden von den ausgezeichneten shapeless-guide Gegeben:Generisches Ableitung von typeclass von unförmigen-Führungs

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 

Das Buch zeigt, wie ein generisches JsonEncoder zu schreiben, dh ein A gegeben, bietet eine JSON Darstellung dieser 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)) 
    } 

Wie ich verstehe, wird der obige Code ableiten allgemein eine case class ‚s JsonEncoder. Allerdings wird es keine sealed trait oder Co-Produkt handhaben:

import net.JsonEncoder 

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

scala> JsonEncoder[A] 
res0: net.JsonEncoder[A] = [email protected] 

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

Dann wird der Text bietet die folgenden Code, wie ich sie verstehe, die allgemein eine JsonEncoder Beispiel für eine JsonEncoder ableiten:

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

jedoch aus dem REPL, war ich noch nicht in der Lage sealed trait oder Co-Produkt ableiten:

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

Warum diese beiden waren i nstances, d. h. Parent und Co, nicht abgeleitet?

Antwort

3

Sie sollten die Obergrenze für den Typ H von genericObjectEncoder entfernen. Wenn Sie es auf einen Untertyp von HList beschränken, schließen Sie die Fälle aus, in denen es sich tatsächlich um einen Coproduct handelt.

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

Jetzt können Sie sehen, dass es funktioniert.

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

scala> JsonEncoder[Parent] 
res0: JsonEncoder[Parent] = [email protected]