如何自动将修改应用于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中案例类的所有/部分字段?的主要内容,如果未能解决你的问题,请参考以下文章
scala如何通过迭代或类似的方式通过修改case类的所有字符串值来复制新的case类?