基于LabelledGeneric 和Keys 类型类
import shapeless.LabelledGeneric
import shapeless.HList
import shapeless.ops.hlist.ToTraversable
import shapeless.ops.record.Keys
case class Person(name: String, age: Int)
def fields[P <: Product, L <: HList, R <: HList](a: P)(
implicit
gen: LabelledGeneric.Aux[P, L],
keys: Keys.Aux[L, R],
ts: ToTraversable.Aux[R, List, Symbol]
): List[(String, String)] = {
val fieldNames = keys().toList.map(_.name)
val values = a.productIterator.toList.map(_.toString)
fieldNames zip values
}
fields(Person("Jean-Luc, Picard", 70))
// : List[(String, String)] = List((name,Jean-Luc, Picard), (age,70))
scastie
IDEA ...显示错误...没有隐式参数
当涉及到类型级代码和宏时,IntelliJ 编辑器内错误突出显示sometimes 不是 100% 准确的。最好将其视为指导,并正确信任 Scala 编译器,因此如果编译器满意但 IJ 不满意,则使用编译器。另一种选择是尝试 Scala Metals,它应该在编译器诊断和编辑器内错误突出显示之间具有一对一的映射。
为什么您使用 LabelledGeneric.Aux、Keys.Aux、ToTraversable.Aux
这是使用一种称为类型类的设计模式。我的建议是通过The Type Astronaut's Guide to Shapeless 特别是Chaining dependent functions 部分工作
依赖类型函数提供了一种计算类型的方法
从另一个。我们可以链接依赖类型的函数来执行
涉及多个步骤的计算。
考虑以下类型之间的依赖关系
input type
|
gen: LabelledGeneric.Aux[P, L],
|
output type
input type
|
keys: Keys.Aux[L, R]
|
output type
注意例如LabelledGeneric 的输出类型L 如何变成Keys 的输入类型。通过这种方式,您可以向编译器显示类型之间的关系,并且作为回报,编译器能够为您提供一个 HList 代表来自 Product 的字段名称,代表特定的案例类,所有这些甚至在程序运行之前。
ToTraversable 是必需的,因此您可以从 HList 取回常规的 Scala List,从而启用以下位
.toList.map(_.name)
希望这至少能给你一点方向。要搜索的一些关键字是:类型类、依赖类型、隐式解析、类型别名Aux pattern、类型成员与类型参数、type refinement 等。Typelevel 社区有一个新的 Discordchannel,您可以在其中获得进一步的指导。