如何自动将修改应用于Scala中案例类的所有/部分字段?

Posted

技术标签:

【中文标题】如何自动将修改应用于Scala中案例类的所有/部分字段?【英文标题】:How to automatically apply modifications to all/some fields of a case class in Scala? 【发布时间】:2021-10-04 17:17:48 【问题描述】:

我目前正在挑战自己在 Scala 和 FP 方面的技能。而今天:

我想出了一个你可能会感兴趣的问题,魔鬼前卫大师 ;)

假设我在 scala 3 中有以下案例类:

type EmailAddress = String // I defined them like that to show I'm interested in
type PhoneNumber = String // ... attributes via their names, not via their types.
case class Person(name: String, emails: List[EmailAddress], phones: List[PhoneNumber])

我想要一个自动转换(几乎)所有字段的方法。 例如,我想使用 Ordering[String] 的 默认给定实例 订购 emails 和使用 指定实例 订购 phones。 理想情况下,我应该能够排除 name field

所以我会得到类似的东西:

/* Below, I represented the kind of parametrization I would like to be able to do 
 * as parameters of the method orderValues,
 * but it could be annotations or meta-programming instead.
 * 
 * An `orderedPerson` can be directly an instance of Person
 * or something else like an OderedEntity[Person], I don't care so far.
 */
val orderedPerson =
  person.orderValues(
    excluded = Set("name"),
    explicitRules = Map(
      // Phones would have a special ordering (reverse is just a dummy value)
      "phones" -> Ordering.String.reverse
    )
  )

// -----

// So we would get:
Person(
  name = "Xiao",
  emails = List("a@a.a", "a@a.b", "a@b.a"),
  phones = List("+86 100 9000 1000", "+86 100 2000 1000")
)

我已经很长时间没有使用反射,而且我还不熟悉元编程,但我愿意接受任何可以帮助我实现这一目标的解决方案。 这是一个学习的好机会!


[编辑]

我的意图是创建一个可用于轻松匿名任何数据的库。

【问题讨论】:

【参考方案1】:

Scala 中的type 关键字只是一个类型别名。您应该使用 https://github.com/estatico/scala-newtype 之类的 newtype 库(或 Scala 3 中的 opaque type)并从 String 派生 Ordering 的隐式实例

estatico/scala-newtype 为例:

import io.estatico.newtype.macros.newtype
import io.estatico.newtype.ops._

@newtype case class Email(string: String)
object Email 
  implicit val ordering: Ordering[Email] = deriving

@newtype case class PhoneNumber(string: String)
object PhoneNumber 
  implicit val ordering: Ordering[PhoneNumber] = deriving[Ordering].reverse

case class Person(name: String, emails: List[Email], phones: List[PhoneNumber]) 
  lazy val orderValues: Person = this.copy(emails = emails.sorted, phones = phones.sorted)


Person(
  "Xiao",
  List(Email("a@a.a"), Email("a@a.b"), Email("a@b.a")),
  List(PhoneNumber("+86 100 9000 1000"), PhoneNumber("+86 100 2000 1000"))
).orderValues

【讨论】:

这是一个非常有趣的答案,很好地回答了我提出的具体问题,谢谢!但是,假设我想发布一个预设排序库,开发人员将如何使用这个库?我可以想象建议开发人员: -> 用新方法扩展类型; ->在自己的不透明类型中根据导入的lib的给定定义给定实例。但它足够用户友好吗?实际上 Ordering 是一个例子,所以我的问题是,对于任何可以像 Ordering 一样使用的类都是通用的(隐式/给定)。 @BRUNIERTerry 您不能扩展案例类。您可以将 @newsubtype 用于子类型关系,但子类型在这里无关紧要。也不需要子类型来为类型编写新方法。方法扩展可以用隐式实现。此外,你总是可以写一个中缀方法a.method(b)作为前缀函数function(a, b) 您并不总是需要从头开始为新类型定义Ordering,您始终可以从现有类型的Ordering 进行转换。事实上,如果您将您的类型建模为现有类型的代数数据类型,那么您可以通过自动派生免费为您的类型获取 Ordering 的默认实例。 Ordering 是一个类型类,类型类支持即席多态性,而不是 OOP 提倡的子类型多态性。想象一下,有另一个 Order 特征在功能上等同于 Ordering,但允许您将 extends 作为子类型,并想象 Person 是一个导入的 3rd 方类型,既不扩展 Order 也不附带 Ordering 实例。 作为第 3 方 lib 用户,您将无法使用 Order 扩展 Person,但仍然可以为 Person 实例化一个 Ordering 实例,以使通用排序功能正常工作.使用 OOP,您也许可以诉诸一些 GoF 模式……但这样对用户友好吗?

以上是关于如何自动将修改应用于Scala中案例类的所有/部分字段?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Java 类的 Spark Scala 数据集

如何将丰富我的库模式应用于 Scala 集合?

scala如何通过迭代或类似的方式通过修改case类的所有字符串值来复制新的case类?

Spark scala Dataframe:如何将自定义类型应用于现有数据框?

Scala案例类伴随对象 - 类型名称冲突

如何在案例类同伴中覆盖应用