2015-11-06 13 views
5

Pracuję nad biblioteką analizy parsowania CSV (tabulate). Wykorzystuje proste klasy typów do kodowania/dekodowania: kodowanie odbywa się na przykład w instancjach CellEncoder (do kodowania pojedynczej komórki) i RowEncoder (w celu zakodowania całych wierszy).Instancje klasy typu wyprowadzającego dla klas przypadków z dokładnie jednym polem

Korzystanie bezkształtne, Znalazłem to całkiem proste do automatycznego uzyskania następujących przypadkach klasy typ:

  • RowEncoder[A] jeśli A jest klasą sprawa, której pola wszyscy mają CellEncoder.
  • Jeśli jest ADT, którego wersje alternatywne mają RowEncoder.
  • CellEncoder[A] Jeśli jest ADT, którego alternatywy wszystkie mają CellEncoder.

Chodzi o to, ten ostatni okazuje się być prawie całkowicie bezużyteczne w rzeczywistych sytuacjach życiowych: alternatywy wykonania ADT są prawie zawsze klas sprawy, a ja nie mogę czerpać CellEncoder dla klasy tak, że ma więcej niż jedno pole .

Co jednak chciałbym zrobić, to wypisz CellEncoder dla klas przypadków, które mają pojedyncze pole, którego typ ma CellEncoder. To by pokryć na przykład Either, scalaz'S \/, koty Xor ...

To, co mam tak daleko:

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

Działa to dobrze, gdy stosowane wprost:

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

Nie mogę jednak uruchomić go w sposób domyślny. Następuje błąd:

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

Próbowałem zostały również następujące, bez dalszych sukcesów:

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

jestem brakuje czegoś? Czy to, co próbuję zrobić, nawet możliwe?

Odpowiedz

3

To trochę skomplikowane, aby uzyskać H wywnioskować poprawnie, ale można to zrobić z instancji <:<:

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

(Zrobiłem się prostą CellEncoder przez wzgląd kompletnego przykładu roboczego.)

To działa, ponieważ R można wywnioskować, gdy kompilator szuka instancji Generic.Aux[A, R], a następnie może prowadzić wnioskowanie o H przy poszukiwaniu wartości dla ev.

+1

Muszę długo i ciężko o tym myśleć, ale mogę potwierdzić, że to działa. Przypadkowo spędziłem dużo czasu w kodzie Circe'a, aby zrozumieć automatyczne wyprowadzanie klas klas przypadków, więc dziękuję za odpowiedź i Circe! –

+0

Zmagam się z podwójnym problemem - pisanie 'CellDecoder' (' String => A'), ponieważ 'R <:

+0

@NicolasRinaudo Nowe pytanie byłoby odpowiednie. –

Powiązane problemy