8

我可以很容易地为这样的密封案例类族一般派生一个编解码器:

import io.circe._
import io.circe.generic.auto._

sealed trait Base
case class X(x: Int) extends Base
case class Y(y: Int) extends Base

object Test extends App {
  val encoded = Encoder[Base].apply(Y(1))
  val decoded = Decoder[Base].apply(encoded.hcursor)
  println(decoded) // Right(Y(1))
}

但是,如果我向基类添加一个类型成员,我就不能再这样做了,即使它受到密封特性的限制:

import io.circe._
import io.circe.generic.auto._

sealed trait Inner
case class I1(i: Int) extends Inner
case class I2(s: String) extends Inner

sealed trait Base { type T <: Inner }
case class X[S <: Inner](x: S) extends Base { final type T = S }
case class Y[S <: Inner](y: S) extends Base { final type T = S }

object Test extends App {
  val encodedInner = Encoder[Inner].apply(I1(1))
  val decodedInner = Decoder[Inner].apply(encodedInner.hcursor) // Ok
  println(decodedInner) // Right(I1(1))

  // Doesn't work: could not find implicit for Encoder[Base] etc
  // val encoded = Encoder[Base].apply(Y(I1(1)))
  // val decoded = Decoder[Base].apply(encoded.hcursor)
  // println(decoded)
}

有没有办法实现我想要的?如果没有,我可以改变什么来获得类似的东西?

4

1 回答 1

1

这不起作用的主要原因是因为你试图从本质上做

Encoder[Base { type T }]

不用说是什么类型T。这类似于期望这个函数编译 -

def foo[A] = implicitly[Encoder[List[A]]]

您需要明确地优化您的类型。

解决此问题的一种方法是使用Aux模式。您不能使用典型type Aux[S] = Base { type T = S },因为在尝试派生实例时不会给您副产品(XY类不能从类型别名扩展)。相反,我们可以通过创建另一个密封特性来绕过它,Aux并让我们的案例类从它扩展。

只要您的所有案例类都扩展 fromBase.Aux而不是直接 from Base,您就可以使用以下滥用.asInstanceOf来安抚类型系统。

sealed trait Inner
case class I1(i: Int) extends Inner
case class I2(s: String) extends Inner

sealed trait Base { type T <: Inner }
object Base {
  sealed trait Aux[S <: Inner] extends Base { type T = S }
  implicit val encoder: Encoder[Base] = {
    semiauto.deriveEncoder[Base.Aux[Inner]].asInstanceOf[Encoder[Base]]
  }
  implicit val decoder: Decoder[Base] = {
    semiauto.deriveDecoder[Base.Aux[Inner]].asInstanceOf[Decoder[Base]]
  }
}

val encoded = Encoder[Base].apply(Y(I1(1)))
val decoded = Decoder[Base].apply(encoded.hcursor)

请注意,这在很大程度上取决于您实际使用类型的方式。我想你不会依赖Encoder[Base]直接调用,而是使用import io.circe.syntax._和调用.asJson扩展方法。在这种情况下,您可以依赖一个Encoder[Base.Aux[S]]实例,该实例将根据正在编码/解码的值进行推断。对于您的用例,以下内容可能就足够了,而无需求助于.asInstanceOf黑客。

implicit def encoder[S <: Inner : Encoder]: Encoder[Base.Aux[S]] = {
  semiauto.deriveEncoder
}

同样,这完全取决于您如何使用实例。我怀疑你实际上需要一个类型成员 in Base,如果你将它移到一个通用参数中,事情会更简单,这样派生者就可以为你找出副产品。

于 2017-04-17T01:34:51.653 回答