2016-04-25 19 views
7

我很容易一般导出的编解码器为一个密封的箱体类家庭这样的:推导瑟茜编解码器为一个密封的箱体类家族,其中碱性状具有(密封)型构件

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

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

+0

如果您尝试使用辅助模式,该怎么办?例如'Aux [A <:Input] = Base {type T = A}'然后从'Aux'扩展?另外,你是否真的需要它是一个类型成员? – pyrospade

+0

事实上,似乎你的案例类可能只是将'Inner'作为它们的参数,而不是'S <:Inner'。 – pyrospade

+0

我添加了一个答案,但后来修改它以添加更多的细节和解释,以及更好的实现。 – pyrospade

回答

1

的主要原因,这并不工作,是因为你正在尝试做基本上

Encoder[Base { type T }] 

不用说T是什么类型。这类似于期望编译此功能 -

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

您需要明确地改进您的类型。

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

只要您的所有案例类别从Base.Aux而不是直接从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 
} 

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

+0

类型成员是有一个超出范围的原因,它当然可以被删除,但它会重载与许多级联类型参数无处不在的语法。肯定可以做不同的事情,但这不是我想的。 –

+0

另外,如何从Base.Aux扩展Aux是同伴的类型成员?辅助只是一个类型的别名,你不能扩展它afaik,我看不出差异 –

+0

但在上面的代码中,Base.Aux是一个特质,而不是一个类型别名... – Blaisorblade