【问题标题】:How to get Scala case class fields and values as (String, String) with Shapeless or Macro如何使用 Shapeless 或 Macro 将 Scala 案例类字段和值作为(字符串,字符串)
【发布时间】:2021-05-14 09:44:59
【问题描述】:

几天来我一直在努力尝试创建一个宏或使用 shapeless 创建一个方法/函数来将字段名称和值提取为元组 [String, String]。

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

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

我想要这样的东西(实际上并不需要成为案例类中的方法)。

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 等我没有那些花哨的新方法 :(

【问题讨论】:

    标签: scala macros shapeless


    【解决方案1】:

    基于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, 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,您可以在其中获得进一步的指导。

    【讨论】:

    • 您的解决方案完美运行。谢谢!问题: 1) IDEA Idea 无法看到隐含的 Attributes[A] 因此尽管编译正常,但它显示如下错误:No implicit arguments of type: Attributes[CaseClass] 我们可能需要添加什么?还是忽略? 3)我想我对你的代码有大致的了解,但我想更多地理解它,你可以解释它的实际作用以及你为什么使用 LabelledGeneric.Aux、Keys.Aux、 ToTraversable.Aux, trait Attributes[T] 等等?我很想能够制作像你这样的解决方案!真的谢谢你!
    • @envy 请查看已编辑的答案。我试图简化解决方案并提供一些进一步的指示。
    • 谢谢! @mario-galic 非常感谢您的详细回答,现在我对它有了更多了解。我会确保阅读那本书并搜索您提到的主题!周末愉快!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    • 1970-01-01
    • 2020-07-13
    • 2015-10-19
    • 2017-11-24
    • 2021-06-24
    相关资源
    最近更新 更多