派生类型 class 实例 class 仅包含一个字段

Deriving type class instances for case classes with exactly one field

我正在开发 CSV 解析库 (tabulate)。它使用简单类型 classes 进行编码/解码:例如,编码是使用 CellEncoder(对单个单元格进行编码)和 RowEncoder(对整行进行编码)的实例完成的。

使用 shapeless,我发现自动派生以下类型 class 实例非常简单:

事实是,最后一个结果在现实生活中几乎完全没用:ADT 的替代方案几乎总是 classes,而我无法推导出 CellEncoder具有多个字段的案例 class。

但是,我希望能够为具有单个字段且类型为 CellEncoder 的案例 class 派生一个 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] = tabulate.CellEncoder$$anon@7941904b

但是我无法让它隐式工作,以下失败:

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

我错过了什么吗?我正在尝试做的事情是否可行?

正确推断 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。)

这是有效的,因为当编译器寻找 Generic.Aux[A, R] 实例时可以推断出 R,然后在寻找 [=] 的值时可以指导 H 的推断17=].