2015-11-06 10 views
5

Ich arbeite an einer CSV-Parsing-Bibliothek (tabulate). Es verwendet einfache Typklassen für die Kodierung/Dekodierung: Die Kodierung erfolgt zum Beispiel mit Instanzen von CellEncoder (um eine einzelne Zelle zu kodieren) und RowEncoder (um ganze Zeilen zu kodieren).Ableiten von Klasseninstanzen für Fallklassen mit genau einem Feld

Mit formlos, ich habe es ziemlich einfach zu finden, um automatisch die folgenden Typklasseninstanzen ableiten:

  • RowEncoder[A] wenn A ein Fall der Klasse, deren Felder alle haben eine CellEncoder.
  • RowEncoder[A] wenn A ein ADT ist, dessen Alternativen alle eine RowEncoder haben.
  • CellEncoder[A] Wenn A ein ADT ist, dessen Alternativen alle CellEncoder haben.

Die Sache ist, dreht sie die letzten fast völlig nutzlos in realen Situationen erwiesen: ein Alternative Fallklasse fast immer ADT ist, und ich kann nicht ein CellEncoder für eine Fall-Klasse abgeleitet werden, die mehr als ein Feld hat .

Was ich jedoch gerne tun könnte, ist die Ableitung einer CellEncoder für Fallklassen, die ein einzelnes Feld haben, dessen Typ eine CellEncoder hat. Das würde deckt zum Beispiel Either, scalaz des \/, Katzen Xor ...

Dies ist, was ich bisher:

implicit def caseClass1CellEncoder[A, H](implicit gen: Generic.Aux[A, H :: HNil], c: CellEncoder[H]): CellEncoder[A] = 
    CellEncoder((a: A) => gen.to(a) match { 
     case h :: t => c.encode(h) 
    }) 

Dies funktioniert gut, wenn explizit verwendet:

case class Bar(xs: String) 
caseClass1CellEncoder[Bar, String] 
res0: tabulate.CellEncoder[Bar] = [email protected] 

Ich kann es jedoch nicht implizit zum Funktionieren bringen, das folgende schlägt fehl:

implicitly[CellEncoder[Bar]] 
>> could not find implicit value for parameter e: tabulate.CellEncoder[Test.this.Bar] 

Ich habe auch versucht die folgenden, ohne mehr Erfolg:

implicit def testEncoder[A, H, R <: H :: HNil](implicit gen: Generic.Aux[A, R], c: CellEncoder[H]): CellEncoder[A] = 
     CellEncoder((a: A) => gen.to(a) match { 
     case h :: t => c.encode(h) 
     }) 

ich etwas fehle? Ist das, was ich versuche, überhaupt möglich?

Antwort

3

Es ist ein wenig schwierig die H gefolgert richtig zu bekommen, aber Sie können es mit einer <:< Instanz tun:

import shapeless._ 

case class CellEncoder[A](encode: A => String) 

implicit val stringCellEncoder: CellEncoder[String] = CellEncoder(identity) 
implicit val intCellEncoder: CellEncoder[Int] = CellEncoder(_.toString) 

case class Bar(xs: String) 

implicit def caseClass1CellEncoder[A, R, H](implicit 
    gen: Generic.Aux[A, R], 
    ev: R <:< (H :: HNil), 
    c: CellEncoder[H] 
): CellEncoder[A] = CellEncoder(
    (a: A) => ev(gen.to(a)) match { 
    case h :: t => c.encode(h) 
    } 
) 

(Ich habe ein einfaches CellEncoder aus Gründen eines kompletten Arbeitsbeispieles aus.

)

Dies funktioniert, weil R geschlossen werden, wenn der Compiler für eine Generic.Aux[A, R] Instanz sucht, und kann dann die Folgerung von H führen, wenn für einen Wert für ev suchen.

+1

Ich muss lange darüber nachdenken, aber ich kann bestätigen, dass es funktioniert. Zufälligerweise habe ich viel Zeit in Circes Code verbracht, um die automatische Typklassenableitung von Fallklassen zu verstehen, also danke sowohl für diese Antwort als auch für Circe! –

+0

Ich kämpfe mit dem Dualen dieses Problems - schreibe einen 'CellDecoder' (' String => A'), da das 'R <:

+0

@NicolasRinaudo Eine neue Frage wäre angebracht. –

Verwandte Themen