如何使用Shapeless或Macro将Scala案例类字段和值获取为(String,String)



我已经挣扎了几天,试图创建一个宏或使用shapeless创建一个方法/函数来提取字段名称和值作为元组[String,String]。

让我们想象一下下面的案例类:

case class Person(name: String, age: Int)

我想要这样的东西(实际上不需要是case类中的方法(。

case class Person(name: String, age: Int) {
def fields: List[(String, String)] = ???
}
// or
def fields[T](caseClass: T): List[(String, String)] = ???

我在这里看到了很多类似的解决方案,但我无法使它与我的(String,String(用例一起工作

我也很感激一些文献来学习和扩展我对宏的知识,我有《Scala编程》(Martin第三版(和《Scala程序设计》(O'REILLY-Dean Wampler&Alex Payne(,只有O'REILLY有一个关于宏的很小的章节,老实说,它非常缺乏。

谢谢!

PD:我使用的是Scala 2.12.12,所以对于case类productElementNames,我没有那些花哨的新方法,比如:(

基于LabelledGenericKeys类型类

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在编辑器中的错误突出显示有时在涉及类型级别代码和宏时不是100%准确。最好将其视为指导,并适当信任Scala编译器,所以如果编译器很满意,但IJ不满意,那么就使用编译器。另一种选择是尝试Scala Metals,它应该在编译器诊断和编辑器内错误突出显示之间有一对一的映射。

为什么使用LabelledGeneric。辅助,按键。Aux,ToTraversable。辅助

这是使用一种称为类型类的设计模式。我的建议是阅读《无定形类型宇航员指南》,特别是关于链接相关函数的部分

依赖类型函数提供了一种计算一种类型的方法来自另一个。我们可以链接依赖类型的函数来执行涉及多个步骤的计算。

考虑以下类型之间的依赖关系

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中获得常规ScalaList

.toList.map(_.name)

希望这至少能给你一点方向。要搜索的一些关键字有:类型类、依赖类型、隐式解析、类型别名Aux-pattern、类型成员与类型参数、类型精化等。类型级别社区有一个新的Discord频道,您可以在这里获得进一步的指导。

最新更新