如何使用 Shapeless 或 Macro 将 Scala case class 字段和值作为 (String, String)

How to get Scala case class fields and values as (String, String) with Shapeless or Macro

几天来我一直在努力尝试创建一个宏或使用 shapeless 创建一个 method/function 以将字段名称和值提取为 Tuple[String, String]。

让我们想象一下下面的情况 class:

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

我想要这样的东西(在 class 的情况下并不真的需要一个方法)。

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 class productElementNames 等的奇特新方法 :(

基于 LabelledGenericKeys 键入 classes

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 ... shows an error ... No implicit arguments

IntelliJ 编辑器内错误突出显示是 not 100% accurate when it comes to type-level code and macros. Best is to consider it as just guidance, and put trust in the Scala compiler proper, so if compiler is happy but IJ is not, then go with the compiler. Another options is to try Scala Metals,它应该在编译器诊断和编辑器内错误突出显示之间具有一对一的映射。

why you used LabelledGeneric.Aux, Keys.Aux, ToTraversable.Aux

这是使用一种称为类型 classes 的设计模式。我的建议是完成 The Type Astronaut's Guide to Shapeless in particular section on Chaining dependent functions

Dependently typed functions provide a means of calculating one type from another. We can chain dependently typed functions to perform calculations involving multiple steps.

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

                input type
                         |
gen: LabelledGeneric.Aux[P, L],
                            |
                            output type
 
      input type
               |
keys: Keys.Aux[L, R]
                  |
                  output type

请注意 LabelledGeneric 的输出类型 L 如何变成 Keys 的输入类型。通过这种方式,您向编译器展示了类型之间的关系,并且在 return 中,编译器能够为您提供一个 HList 表示来自 Product 的字段名称,表示特定情况 [=46] =],所有这一切都发生在程序运行之前。

ToTraversable 是必需的,因此您可以从 HList 中取回常规 Scala List,从而启用以下位

.toList.map(_.name)

希望这至少能给您一些指导。要搜索的一些关键字是:类型 classes、依赖类型、隐式解析、类型别名 , type members vs type parameters, , etc. Typelevel community has a new Discord channel,您可以在其中获得进一步的指导。