I'm working on a CSV parsing library (tabulate). It uses simple type classes for encoding / decoding: encoding, for example, is done with instances of CellEncoder
(to encode a single cell) and RowEncoder
(to encode entire rows).
Using shapeless, I've found it pretty straightforward to automatically derive the following type class instances:
RowEncoder[A]
if A
is a case class whose fields all have a CellEncoder
.RowEncoder[A]
if A
is an ADT whose alternatives all have a RowEncoder
.CellEncoder[A]
if A
is an ADT whose alternatives all have a CellEncoder
.The thing is, this last one turns out to be almost entirely useless in real life situations: an ADT's alternatives are almost always case classes, and I cannot derive a CellEncoder
for a case class that has more than one field.
What I'd like to be able to do, however, is derive a CellEncoder
for case classes that have a single field whose type has a CellEncoder
. That would cover, for example, Either
, scalaz's \/
, 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 can't however get it to work implicitly, the following fails:
implicitly[CellEncoder[Bar]]
>> could not find implicit value for parameter e: tabulate.CellEncoder[Test.this.Bar]
I've also tried the following, with no more 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 what I'm trying to do even possible?
It's a little tricky to get the H
inferred correctly, but you can do it with a <:<
instance:
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've made up a simple CellEncoder
for the sake of a complete working example.)
This works because R
can be inferred when the compiler is looking for an Generic.Aux[A, R]
instance, and can then guide the inference of H
when looking for a value for ev
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With