可扩展的记录类型

Extensible record types

我正在尝试使用 shapeless 的可扩展记录进行简单练习。

这是一个名为 Projection 的类型类,它应该能够在某种程度上结合 UpdaterRemover 的功能:

import shapeless._
import shapeless.tag._
import shapeless.record._
import shapeless.labelled._
import shapeless.ops.record._
import shapeless.syntax._
// Probably way too many imports

trait Projection[A <: HList, K, V] {
    type B <: HList

    def to(a: A, v: V): B
    def from(b: B): A
}

object Projection {
    type Aux[A <: HList, K, V, B0 <: HList] = Projection[A, K, V] { type B = B0 }

    type Key[K] = Symbol with Tagged[K]
    type F[K, V] = V with FieldType[Key[K], V]

    implicit def mkProjection[A <: HList, K, V, B0 <: HList](implicit
        keyWitness: Witness.Aux[K],
        updater: Updater.Aux[A, F[K, V], B0],
        remover: Remover.Aux[B0, K, (V, A)]
    ): Projection.Aux[A, K, V, B0] = new Projection[A, K, V] {
        type B = B0

        def from(b: B0): A = b - keyWitness
        def to(a: A, v: V): B0 = a + field[Key[K]](v)
    }
}

我比较简单的测试

import Projection._

val thirdFieldWitness = Witness("thirdField")
val projector = implicitly[Projection[HNil, thirdFieldWitness.T, Boolean]]

不幸的是失败并出现错误

could not find implicit value for parameter e: Projection[shapeless.HNil,ProjectionSpec.this.thirdFieldWitness.T,Boolean]
[error]         val projector = implicitly[Projection[HNil, thirdFieldWitness.T, Boolean]]

-Xlog-implicits说明原因:

ProjectionSpec.scala:18:35: record.this.Remover.mkRemover is not a valid implicit value for shapeless.ops.record.Remover.Aux[Boolean with shapeless.labelled.FieldType[Projection.Key[ProjectionSpec.this.thirdFieldWitness.T],Boolean] :: shapeless.HNil,ProjectionSpec.this.thirdFieldWitness.T,(Boolean, shapeless.HNil)] because:
[info] hasMatchingSymbol reported error: No field String("thirdField") in record type Boolean with shapeless.labelled.FieldType[Projection.Key[ProjectionSpec.this.thirdFieldWitness.T],Boolean] :: shapeless.HNil
[info]         val projector = implicitly[Projection[HNil, thirdFieldWitness.T, Boolean]]

请帮助我理解此消息并告诉我如何修复它。

是否有更简单的方法来对标记的仿制药进行这种扩展和缩短?

-Xlog-implicits外,一种更标准的隐式调试方法是手动解析它们并查看编译错误。

尝试

object Projection {
  type Aux[A <: HList, K, V, B0 <: HList] = Projection[A, K, V] { type B = B0 }

  type Key[K] = Symbol with Tagged[K]
  type F[K, V] = FieldType[Key[K], V]

  implicit def mkProjection[A <: HList, K, V, B0 <: HList](implicit
    keyWitness: Witness.Aux[Key[K]],
    updater: Updater.Aux[A, F[K, V], B0],
    remover: Remover.Aux[B0, Key[K], (V, A)]
  ): Projection.Aux[A, K, V, B0] = new Projection[A, K, V] {
    type B = B0

    def from(b: B0): A = b - keyWitness
    def to(a: A, v: V): B0 = a + field[Key[K]](v)
  }
}

然后

implicitly[Projection.Aux[HNil, "thirdField", Boolean, Record.`'thirdField -> Boolean`.T]]

编译。

但是虽然implicitly[thirdFieldWitness.T =:= "thirdField"]

implicitly[Projection.Aux[HNil, thirdFieldWitness.T, Boolean, Record.`'thirdField -> Boolean`.T]]

仍然无法编译。但是手动解决了

implicitly[Projection.Aux[HNil,
  thirdFieldWitness.T,
  Boolean,
  Record.`'thirdField -> Boolean`.T
]](Projection.mkProjection(
  implicitly[Witness.Aux[Witness.`'thirdField`.T]],
  implicitly[Updater.Aux[HNil, FieldType[Witness.`'thirdField`.T, Boolean], Record.`'thirdField -> Boolean`.T]],
  implicitly[Remover.Aux[Record.`'thirdField -> Boolean`.T, Witness.`'thirdField`.T, (Boolean, HNil)]]
))

编译。事情似乎是 implicitly[Witness.Aux[Key["thirdField"]]] 编译但 implicitly[Witness.Aux[Key[thirdFieldWitness.T]]] 没有(“Symbol with Tagged[thirdFieldWitness.T] 不是单例类型”)。

如果添加

可以修复编译
implicit def extraWitness[S <: String](implicit 
  w: Witness.Aux[S]
): Witness.Aux[Symbol @@ S] = Witness.mkWitness(tag[S](Symbol(w.value)))

我会使用标准的基于符号的 API

object Projection {
  type Aux[A <: HList, K, V, B0 <: HList] = Projection[A, K, V] { type B = B0 }

  type F[K, V] = FieldType[K, V]

  implicit def mkProjection[A <: HList, K, V, B0 <: HList](implicit
    keyWitness: Witness.Aux[K],
    updater: Updater.Aux[A, F[K, V], B0],
    remover: Remover.Aux[B0, K, (V, A)]
  ): Projection.Aux[A, K, V, B0] = new Projection[A, K, V] {
    type B = B0

    def from(b: B0): A = b - keyWitness
    def to(a: A, v: V): B0 = a + field[K](v)
  }
}

implicitly[Projection.Aux[HNil, Witness.`'thirdField`.T, Boolean, Record.`'thirdField -> Boolean`.T]]

val thirdFieldWitness = Witness('thirdField)
implicitly[Projection.Aux[HNil, thirdFieldWitness.T, Boolean, Record.`'thirdField -> Boolean`.T]]