如何使用 Shapeless 或 Macro

问题描述

几天来我一直在努力尝试创建一个宏或使用 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,所以我没有那些用于案例类 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 编辑器中的错误突出显示 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,|
                            output type
 
      input type
               |
keys: Keys.Aux[L,R]
                  |
                  output type

注意例如L的输出类型LabelledGeneric如何变成Keys的输入类型。通过这种方式,您向编译器展示了类型之间的关系,作为回报,编译器能够为您提供一个 HList,代表来自 Product 的字段名称,代表特定案例类,所有这些都在程序甚至可以运行。

需要

ToTraversable 以便您可以从启用以下位的 List 中取回常规的 Scala HList

.toList.map(_.name)

希望这至少能给你一点方向。要搜索的一些关键字是:类型类、依赖类型、隐式解析、类型别名 Aux pattern、类型成员 vs 类型参数、type refinement 等。Typelevel 社区有一个新的 Discord channel 其中你可以得到进一步的方向。