I'm currently challenging myself to skill up in Scala and FP. And today:
Let's say I have the following case class in 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])
I would like to have a method that automatically transform (almost) all fields.
For example, I would like to order emails
with the default given instance of Ordering[String] and phones
with a specified one.
Ideally I should be able to exclude name
field.
So I would get something like:
/* 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")
)
I haven't used Reflection for a long time and I'm not yet familiar with Meta-Programming, but I'm open to any solution that can help me to achieve that. It's a good opportunity for learning !
[Edit]
My intentional intent was to have a library that can be use to easily anonymize any data.
The type
keyword in Scala is just a type alias. You should use a newtype
library like https://github.com/estatico/scala-newtype (or opaque type
in Scala 3) and derive implicit instances of Ordering from String
Example with 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