2016-04-25 51 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