以下内容来自写得很好的 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)
}
上面的代码在添加我的余积编码器尝试后,可以编译,但无法派生余积:
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
无法正常工作?
最佳答案
Sealed families of case classes与联产品的不同之处就像案例类与产品的不同一样。您为案例类添加了 hlistObjectEncoder
和 genericObjectEncoder
,从逻辑上讲,您需要类似的余积。在您的代码中,您添加了余积的派生,但您忘记告诉 JsonEncoder
,如果可以派生余积,则可以派生出以该余积作为通用表示形式的密封案例类系列。您可以添加此内容
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
需要 H
的 JsonObjectEncoder
,它代表您所生成的 Coproduct 的每个元素想要编码,但您只为具有 genericObjectEncoder
的案例类提供 JsonEncoder
。因此,您甚至无法派生诸如 B :+: CNil
之类的内容,例如,它是 A
的通用表示形式。解决方案可能是更改 genericObjectEncoder
以返回 JsonObjectEncoder
或更改 coproductEncoder
以接受输入 JsonEncoder
对于H
。两种策略有不同的结果,选择最适合你的一种。我更喜欢第一个解决方案,因为我认为您希望联产品的所有元素都具有 JsonObject
编码,但这取决于您。第一个解决方案的代码是:
implicit def genericObjectEncoder[A, H <: HList](
implicit
generic: LabelledGeneric.Aux[A, H],
hEncoder: Lazy[JsonObjectEncoder[H]]
): JsonObjectEncoder[A] =
createObjectEncoder { value =>
hEncoder.value.encode(generic.to(value))
}
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]
): JsonObjectEncoder[H :+: T] = createObjectEncoder {
case Inl(h) => hEncoder.value.encode(h)
case Inr(t) => tEncoder.encode(t)
}
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) )
}
关于scala - 余积的类型类推导?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41537001/