scala - 余积的类型类推导?

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

上面的代码在添加我的余积编码器尝试后,可以编译,但无法派生余积:

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与联产品的不同之处就像案例类与产品的不同一样。您为案例类添加了 hlistObjectEncodergenericObjectEncoder,从逻辑上讲,您需要类似的余积。在您的代码中,您添加了余积的派生,但您忘记告诉 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 需要 HJsonObjectEncoder,它代表您所生成的 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/

相关文章:

scala - Scala 中的代理/委托(delegate)

java - 如何测试方法如何处理并发调用

scala - 在scala 2.13中,为什么有时无法推断TypeTags?以及如何从变量符号构造一个变量?

scala - 在 Scala 中使用 Shapeless 折叠不同类型的列表

scala - 无形:从副产品映射到不同的副产品

scala - 如何在scala中构建无限不可变树

java - Heroku JVM 调优

scala - List 是怎样的一个单子(monad)?

scala - 如何使用一元类型构造函数推断无形状记录值的内部类型?

scala - Shapeless:使用 Coproduct 拥有自己的 HList 约束