samedi 15 janvier 2022

Apply a method to all fileds of a case class of a specific type

I'll give an example/motivation for what I'm trying to do.

Imagine I have a Money case class, and a way to convert it's value from one currency to another:

case class Money(currency: String, amount: BigDecimal) {
  def convert(date: LocalDate)(using cc: CurrencyConverter): Money = {...}
}

The CurrencyConverter is a Map with daily exchange rates between currency, so that I can use the convert method to go for example from something like Money("EUR", BigDecimal(100)) to Money("USD", BigDecimal(114.13)).

Then, I have a lot of different case classes with one or more Money fields. For example:

case class House(date: LocalDate, price: Money, city: String)

case class MarketData(
    exchange: String,
    ticker: String,
    date: LocalDate,
    closingPrice: Money,
    openPrice: Money
)

Since I could data where the data is in a lot of different currencies, for example

val house1 = House(LocalDate.parse("2010-01-01"), Money("USD", BigDecimal(400000)), "San Francisco")
val house2 = House(LocalDate.parse("2018-01-01"), Money("EUR", BigDecimal(200000)), "Rome")

it would be useful to give each case class a method convertCurrency to allow them to convert their Money fields to a common currency.

I could define a trait with an abstract method and manually override it inside each case class definition, like so:


trait MoneyConverter[A] {
  def convertCurrency(using cc: CurrencyConverter): A
}

case class House(date: LocalDate, price: Money, city: String)
  extends MoneyConverter[House] {
  override def convertCurrency(using cc: CurrencyConverter): House =
    House(date, price.convert(date), city)
}

case class MarketData(
                       exchange: String,
                       ticker: String,
                       date: LocalDate,
                       closingPrice: Money,
                       openPrice: Money
                     ) extends MoneyConverter[MarketData] {
  override def convertCurrency(using cc: CurrencyConverter): MarketData =
    MarketData(
      exchange,
      ticker,
      date,
      closingPrice.convert(date),
      openPrice.convert(date)
    )
}

but it seams like I'm repeating myself. Each method is doing basically the same: it is applying the convert method to each Money field, and leaving the other fields as they were.

So my question is: Is there a way for me to describe this behavior for a generic class A, so that I can then just extend the MoneyConverter trait, without having to manually override the convertCurrency method for each case class?

Aucun commentaire:

Enregistrer un commentaire