问题描述
我正在研究 CSV 解析库(tabulate).它使用简单的类型类进行编码/解码:例如,编码是通过 CellEncoder
(编码单个单元格)和 RowEncoder
(编码整行)的实例完成的.
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).
使用 shapeless,我发现自动派生以下类型类实例非常简单:
Using shapeless, I've found it pretty straightforward to automatically derive the following type class instances:
RowEncoder[A]
如果A
是一个 case 类,其字段都有一个CellEncoder
.RowEncoder[A]
如果A
是一个 ADT,其替代项都有一个RowEncoder
.CellEncoder[A]
如果A
是一个 ADT,其替代项都有一个CellEncoder
.
RowEncoder[A]
ifA
is a case class whose fields all have aCellEncoder
.RowEncoder[A]
ifA
is an ADT whose alternatives all have aRowEncoder
.CellEncoder[A]
ifA
is an ADT whose alternatives all have aCellEncoder
.
问题是,最后一个在现实生活中几乎完全没用:ADT 的替代品几乎总是案例类,我无法为具有以下特征的案例类派生 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.
但是,我希望能够为具有类型为 CellEncoder
的单个字段的案例类派生一个 CellEncoder
.例如,这将涵盖 Either
、scalaz 的 \/
、cats 的 Xor
...
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
...
这是我目前所拥有的:
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] = 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?
推荐答案
正确推断 H
有点棘手,但您可以使用 <:<
实例:
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)
}
)
(为了完整的工作示例,我编写了一个简单的 CellEncoder
.)
(I've made up a simple CellEncoder
for the sake of a complete working example.)
这是可行的,因为当编译器在寻找 Generic.Aux[A, R]
实例时可以推断 R
,然后可以指导 H
查找 ev
的值时.
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
.
这篇关于为只有一个字段的案例类派生类型类实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!