2015-11-06 3 views
5

Я работаю над библиотекой анализа CSV (tabulate). Он использует классы простого типа для кодирования/декодирования: например, кодирование выполняется с помощью экземпляров CellEncoder (для кодирования одной ячейки) и RowEncoder (для кодирования целых строк).Производящие экземпляры класса класса для классов классов с одним полем

Использование бесформенный, я нашел, что это довольно просто, чтобы автоматически получить следующие экземпляры класса типа:

  • RowEncoder[A] если A случай класс, все поля имеют CellEncoder.
  • RowEncoder[A], если A является ADT, у всех альтернатив есть RowEncoder.
  • CellEncoder[A] если A является ADT, у всех альтернатив есть CellEncoder.

Дело в том, этот последний оказывается почти полностью бесполезным в реальных жизненных ситуациях: альтернативы АТД являются почти всегда тематические классы, и я не могу вывести CellEncoder для случая класса, который имеет более чем одно поле ,

Что бы я хотел сделать, однако, вывести CellEncoder для классов классов, которые имеют одно поле, тип которого имеет CellEncoder. Это будет включать, например, Either, scalaz годов \/, кошки Xor ...

Это то, что я до сих пор:

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

Это прекрасно работает при использовании в явном виде:

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

Я не могу заставить его работать неявно:

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

Я также попытался следующие, не больше успеха:

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

я упускаю что-то? Является ли то, что я пытаюсь сделать, даже возможно?

ответ

3

Это немного сложнее, чтобы получить H правильно судить, но вы можете сделать это с помощью <:< например:

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

(Я сделал простой CellEncoder для полного рабочего примера.)

Это работает, потому что R можно сделать вывод, когда компилятор ищет Generic.Aux[A, R] экземпляра, а затем может направлять вывод о H при поиске значения для ev.

+1

Мне нужно долго думать об этом, но я могу подтвердить, что он работает. Кстати, я много времени проводил в коде Цирце, чтобы понять автоматическое определение классов классов классов, так что спасибо за этот ответ и Circe! –

+0

Я борюсь с дуальным из этой проблемы - написав «CellDecoder» ('String => A'), поскольку в этом контексте« R <:

+0

@NicolasRinaudo Новый вопрос будет уместным. –

Смежные вопросы