2016-04-25 5 views
7

ich einfach allgemein einen Codec für ein abgedichtetes Gehäuse Familie wie folgt ableiten:für ein abgedichtetes Gehäuse Familie ableiten circe Codec, wo die Basismerkmal a (verschlossen) Typ hat Mitglied

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

Allerdings, wenn ich hinzufügen ein Typ Element der Basisklasse kann ich es nicht mehr tun, auch wenn es von einem versiegelten Zug begrenzt ist:

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

gibt es eine Weise, die ich erreichen kann, was ich will? Wenn nicht, was kann ich ändern, um etwas Ähnliches zu bekommen?

+0

Was ist, wenn Sie mit dem Aux-Muster versucht haben? z.B. 'type Aux [A <: Input] = Base {Typ T = A}' und dann von 'Aux'? Brauchst du es wirklich, um ein Mitglied des Typs zu sein? – pyrospade

+0

In der Tat, scheint Ihr Fall Klassen könnte ein 'Inneres 'als ihr Argument anstelle eines' S <: Inner nehmen. – pyrospade

+0

Ich habe eine Antwort hinzugefügt, aber seitdem überarbeitet, um weitere Details und Erklärungen sowie eine bessere Implementierung hinzuzufügen. – pyrospade

Antwort

1

Der Hauptgrund, dies nicht ist nicht funktioniert, weil Sie im Wesentlichen

tun versuchen zu
Encoder[Base { type T }] 

ohne zu sagen, welche Art T ist. Dies entspricht der erwarteten Kompilierung dieser Funktion -

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

Sie müssen Ihren Typ explizit verfeinern.

Eine Möglichkeit, dies zu erreichen, ist mit dem Aux Muster. Sie können das typische type Aux[S] = Base { type T = S } nicht verwenden, da dies Ihnen nicht das Koppelprodukt gibt, wenn Sie versuchen, die Instanz abzuleiten (die Klassen X und Y können nicht von einem Typalias ausgehen). Stattdessen könnten wir es umgehen, indem wir ein weiteres versiegeltes Merkmal wie Aux erstellen und unsere Fallklassen daraus erweitern.

Solange alle Ihre Fallklassen von Base.Aux erweitern, anstatt direkt von Base, können Sie die folgende verwenden, die Mißbräuche .asInstanceOf den Typ System zu beschwichtigen.

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) 

Beachten Sie, dass viel davon abhängt, wie Sie Ihre Typen tatsächlich verwenden. Ich könnte mir vorstellen, dass Sie sich nicht darauf verlassen würden, Encoder[Base] direkt anzurufen und stattdessen import io.circe.syntax._ verwenden und die .asJson Erweiterungsmethode aufrufen würde. In diesem Fall können Sie sich möglicherweise auf eine Encoder[Base.Aux[S]] Instanz verlassen, die abhängig von dem Wert, der codiert/decodiert wird, abgeleitet wird. Etwas wie das Folgende kann für Ihren Anwendungsfall ausreichen, ohne auf .asInstanceOf Hacks zurückgreifen zu müssen.

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

Wieder hängt alles davon ab, wie Sie die Instanzen verwenden. Ich bin skeptisch, dass Sie tatsächlich ein Typ-Mitglied in Base brauchen, Dinge wären einfacher, wenn Sie es zu einem generischen Parameter verschoben würden, so dass der Entführer das Koprodukt für Sie herausfinden könnte.

+0

Der Typ Mitglied ist aus einem Grund, der außerhalb des Geltungsbereichs ist, kann sicherlich entfernt werden, aber es würde Syntax mit vielen kaskadierenden Typ Parameter überall überladen. Es kann sicherlich anders gemacht werden, aber das ist nicht der Punkt, denke ich. –

+0

Wie würden Sie auch von Base.Aux ausgehen, wo Aux ein Typ-Mitglied des Companion ist? Aux ist nur ein Typ Alias, Sie können es nicht erweitern afaik, ich sehe den Unterschied sowieso nicht –

+0

Aber im obigen Code ist Base.Aux ein Merkmal, kein Typ Alias ​​... – Blaisorblade

Verwandte Themen