由Shapeless提供支持的通用字段访问器

时间:2018-12-06 17:17:35

标签: scala shapeless

我正在尝试基于LabelledGeneric实现一个方便的通用字段访问器。 用法应如下所示:

case class Foo(aha: String, uhu: Double, ehe: Int)
case class Bar(uhu: Double, ahu: Boolean)

val foo: Foo = ???
val bar: Bar = ???

val uhuGenField = new GenField('uhu)
val uhuFooAccess = uhuGenField.from[Foo]
val uhuBarAccess = uhuGenField.from[Bar]

def someFunWithUhu[X](xs: Seq[X], access: uhuGenField.Access[X]) = ??? 

我花了一些时间试图弄清楚如何实现这种行为。 最终我想到了这种方法:

import shapeless._
import shapeless.ops.record.Selector

final class GenField[V](val fieldName: Symbol) {
  val fieldWitness = Witness(fieldName)
  type FieldNameType = fieldWitness.T

  trait Access[C] {
    def get(c: C): V
  }

  def from[C](implicit lg2hl: LGtoHL[C]): Access[C] = new Access[C] {
    override def get(c: C): V = {
      val labelledGeneric = lg2hl.labelledGeneric
      val selector = Selector.mkSelector[labelledGeneric.Repr, FieldNameType, V]
      selector(labelledGeneric.to(c))
    }
  }
}

// I need something like this to enable syntax like
//   genField.from[DesiredClass]
// i.e. to "latch" Repr inside a single instance
// and to don't pass it explicitly to `from` method. 
sealed trait LGtoHL[A] {
  type Repr <: HList
  val labelledGeneric: LabelledGeneric.Aux[A, Repr]
}

object LGtoHL {
  implicit def mkLGtoHL[A, ARepr <: HList](implicit lg: LabelledGeneric.Aux[A, ARepr]): LGtoHL[A] = {
    new LGtoHL[A] {
      override type Repr = ARepr
      override val labelledGeneric: LabelledGeneric.Aux[A, Repr] = lg
    }
  }
}

从我的角度来看,此解决方案应该可以,但仍然无法正常工作。 编译失败,并显示以下错误消息:

Error:(17, 41) lg2hl.Repr is not an HList type
        val selector = Selector.mkSelector[labelledGeneric.Repr, FieldNameType, V]

为什么它抱怨lg2hl.Repr is not an HList typeReprLGtoHL中明确定义为type Repr <: HList。 我的代码有什么问题?

非常感谢您的帮助。

1 个答案:

答案 0 :(得分:2)

为什么lenses还不够?

import shapeless.{Lens, lens}

case class Foo(aha: String, uhu: Double, ehe: Int)
case class Bar(uhu: Double, ahu: Boolean)

val foo: Foo = Foo("a", 1.0, 2)
val bar: Bar = Bar(3.0, true)

val fooUhu: Lens[Foo, Double] = lens[Foo] >> 'uhu
val barUhu: Lens[Bar, Double] = lens[Bar] >> 'uhu

fooUhu.get(foo) // 1.0
barUhu.get(bar) // 3.0

错误消息

  

lg2hl.Repr不是HList类型

来自这里:https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/generic.scala#L511

u.baseType(HConsSym)现在为NoType

我猜测GenField[V](val fieldName: Symbol)将无法工作,因为必须在编译时知道fieldName中的Witness(fieldName)。例如

lens[Foo] >> 'uhu

有效但是

val uhu: Witness.`'uhu`.T = 'uhu.narrow
lens[Foo] >> uhu

没有。这就是为什么通过宏实现镜头,见证,LabelledGeneric的原因。