副产品的类型推导?

时间:2017-01-08 19:40:01

标签: scala shapeless

从精心编写的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

该文本演示了如何为上述数据结构派生JsonEncoder类型类实例:

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)

  // omitted other instances (String, Boolean, etc.)

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

我尝试定义coproductEncoder

 implicit val cnilEncoder: JsonObjectEncoder[CNil] =
    createObjectEncoder { cnil => throw new RuntimeException("Inconceivable!") }

  implicit def coproductEncoder[H, T <: Coproduct](
    implicit
    hEncoder: Lazy[JsonObjectEncoder[H]],
    tEncoder: JsonObjectEncoder[T]
  ): JsonEncoder[H :+: T] = createObjectEncoder {
    case Inl(h) => hEncoder.value.encode(h)
    case Inr(t) => tEncoder.encode(t)
  }

上面的代码,在添加我的coproduct编码器尝试后,编译,但它无法派生副产品:

sealed trait A
case class B(x: Double) extends A

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

您能否提一下我coproductEncoder无效的原因?

1 个答案:

答案 0 :(得分:4)

Sealed families of case classes与Coproducts的不同之处在于案例类与Products不同。您为案例类添加了hlistObjectEncodergenericObjectEncoder,从逻辑上讲,您需要类似于coproduct的内容。在您的代码中,您添加了Coproducts的派生,但是您忘了告诉JsonEncoder,如果可以派生Coproduct,那么可以派生一个密封的案例类族,其中Coproduct作为通用表示。你可以用

添加它
implicit def genericFamilyEncoder[A, C <: Coproduct](
  implicit
  generic: Generic.Aux[A, C],
  cEncoder: Lazy[JsonObjectEncoder[C]]
): JsonEncoder[A] =
  instance { value =>
    cEncoder.value.encode(  generic.to(value) )
  }

请注意,这不会直接编译,因为您的coproductEncoder需要JsonObjectEncoder H代表您要编码的Coproduct的每个元素,但您只提供{ {1}}用于JsonEncoder的案例类。因此,您甚至无法导出类似genericObjectEncoder的内容,例如B :+: CNil的通用表示形式。解决方案可以是更改A以返回genericObjectEncoder或更改JsonObjectEncoder以便为coproductEncoder输入JsonEncoder。这两种策略有不同的结果,选择最适合你的一种。我更喜欢第一种解决方案,因为我认为您希望产品的所有元素都具有H编码,但这取决于您。第一个解决方案的代码是:

JsonObject