问题 如何在编译时使用shapeless将case类字段的名称作为字符串/符号?


我想以某种方式在编译时获取val中的case类的字段的名称(可能是单例类型的字符串或符号?)。

类似于以下内容:

import shapeless._
case class MyClass(field1: String, field2: Int)
val field1Lens = lens[MyClass] >> 'field1 
// val name = field1Lens.name // it should be "field1", aka 'field1.name

我没有必要使用镜头,任何有效的技术都很好(有些东西 LabelledGeneric?)。我想有一些东西,我可以获得案例类字段的名称,而无需单独指定。这样,如果我重构的名称 field1 班上的成员, name 相应地改变。

当然以下不起作用,因为宏在编译时不知道符号的名称:

val name = 'field1
val field1Lens = lens[MyClass] >> name // can't possibly work

我试过了 lens[MyClass] >> name.narrow 但它也不起作用

这就是我目前正在做的事情,当然我不喜欢它:

// If I change the name of the field, compilation fails 
// and I'm forced to check this line of code so I can change the string in the line below
protected val fieldNameCheck = lens[X].someField
val someField = "someField"

编辑:好的,我看了看 加布里埃莱的问题,并通过使用 Keys 我能够获得包含记录(标记)键的HList。 我需要的是获得一个特定字段,而不是包含所有字段的列表。

我正在尝试使用 select 获得一个特定的密钥,但到目前为止我没有成功

import shapeless._
import shapeless.syntax.singleton._
import shapeless.ops.record._

case class Foo(bar: String, baz: Boolean)
val labl = LabelledGeneric[Foo]
val keys = Keys[labl.Repr].apply

// the following doesn't work
// val bar = 'bar.narrow
// keys.select[bar.type]
// keys.get('bar)

11810
2018-03-15 09:07


起源

可能重复 从LabelledGeneric实例中提取标签值 - Gabriele Petronella
我不认为我理解动机 - 听起来你想写文字 'bar 在代码中的一个位置,这样你就不必在另一个地方写它,我不确定这对重构有什么帮助。如果您担心会员名称的变化,位置选择器可能是更好的选择。 - Travis Brown
长话短说:序列化库。我不想被迫编写自定义序列化程序来执行库已经执行的操作(使用字段名称作为键等编写json对象),因为我需要在其他地方使用该字段的名称(例如,部分更新时)关于elasticsearch)。我只写了一次文字'bar,但如果case类发生了变化,代码仍会编译。我需要大量的测试,以确保每一个 'bar 我需要有一个 (bar: T) case class param完全相同:每个类的每个字段都有一个。 - Giovanni Caporaletti
@TravisBrown在哪里可以找到有关位置选择器的信息? - Giovanni Caporaletti
@TrustNoOne啊,在那种情况下我会推荐 val barKey = Witness('bar) 某处然后例如 lens[Foo] >> barKey,这应该工作得很好。 - Travis Brown


答案:


争论的 >> 是一个 Witness 将成员名称捕获为编译时符号。当你写作 >> 'bar,符号文字隐式转换为a Witness,这通常是你想要的,但你也可以自己提供:

scala> case class Foo(bar: String, baz: Boolean)
defined class Foo

scala> val barKey = shapeless.Witness('bar)
barKey: shapeless.Witness.Aux[shapeless.tag.@@[Symbol,String("bar")]] = ...

scala> shapeless.lens[Foo] >> barKey
res0: shapeless.Lens[Foo,String] = shapeless.Lens$$anon$7@344bfb60

正如我在上面的评论中提到的,您可能也对位置选择器感兴趣:

scala> shapeless.lens[Foo] >> shapeless.nat._1
res1: shapeless.Lens[Foo,Boolean] = shapeless.Lens$$anon$7@16e9d434

甚至只是:

scala> shapeless.lens[Foo] >> 1
res2: shapeless.Lens[Foo,Boolean] = shapeless.Lens$$anon$7@4be29007

这些不要求您在代码中的任何位置写入成员名称,但如果重新排列成员,则会遇到麻烦。


6
2018-03-15 17:35



我最终创建了一个2行的方法来做我想要的,这是我自己的答案供参考。 - Giovanni Caporaletti
这对我正在做的事情非常有帮助,因为你有很多关于无形的答案。谢谢! - Matthew Pickering


好的,多亏了特拉维斯的评论我得到了它的工作:

import shapeless._
case class MyClass(field1: String, field2: Int)

def fieldName[A](fieldKey: Witness.Lt[_ <: Symbol])(implicit mkl: MkFieldLens[A, fieldKey.T]) = {
  lens[A] >> fieldKey
  fieldKey.value.name
}

println(fieldName[MyClass]('field1))

3
2018-03-15 17:33



我想你可以不用了 lens[A] >> fieldKey 因为它未分配。编译器解析了 MkFieldLens 实例足以确保该字段在类中。 - Matthew Pickering