Getting instances of a type class for case classes with exactly one field

I am working on a CSV parsing library ( tabulate ). It uses simple class types for encoding / decoding: for example, encoding is performed with instances of CellEncoder (for encoding a single cell) and RowEncoder (for encoding whole strings).

Using formlessness, I found it pretty simple to automatically get the following type class instances:

  • RowEncoder[A] if A is the case class, all fields of which have CellEncoder .
  • RowEncoder[A] , if A is an ADT, all alternatives have a RowEncoder .
  • CellEncoder[A] , if A is an ADT, all alternatives have CellEncoder .

The fact is that the latter is almost useless in real life situations: ADT alternatives are almost always case classes, and I can not get CellEncoder for the case class, which has more than one field.

However, I would like to be able to make CellEncoder for case classes that have one field of type CellEncoder . This will cover e.g. Either , scalaz \/ , cats' Xor ...

This is what I have so far:

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

This works fine when used explicitly:

 case class Bar(xs: String) caseClass1CellEncoder[Bar, String] res0: tabulate.CellEncoder[Bar] = tabulate.CellEncoder$$anon$2@7941904b 

I cannot get it to work implicitly, the following:

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

I also tried the following, without any success:

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

Am I missing something? Is this what I'm trying to do is even possible?

+5
source share
1 answer

It's a little tricky to get the output of H correctly, but you can do it with an instance of <:< :

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

(I created a simple CellEncoder for a complete working example.)

This works because R can be inferred when the compiler searches for an instance of Generic.Aux[A, R] and can then infer the output of H when looking for the value for ev .

+3
source

Source: https://habr.com/ru/post/1235350/


All Articles