1

I really like Scala for its flexibility and conciseness. With this definition of a money amount:

case class MoneyAmount(amount: Double, currency: Currency)

trait Currency
case object EUR extends Currency
case object USD extends Currency

You can create instances easily:

val m1 = MoneyAmount(100, EUR)

With the following definition in scope its even easier:

implicit class DoubleMoney(amount: Double) {
  def apply(currency: Currency) = MoneyAmount(amount, currency)
}

val m2 = 100 (EUR)
val m3 = 100 (USD)

My question is: Is there a way that the following is possible:

val m3 = 100 EUR // does not compile!

without defining functions for each currency (EUR, USD, ...)?

Erik
  • 2,888
  • 2
  • 18
  • 35
  • 1
    You can do `100.EUR` or add an indirection layer `100 money EUR`. The first one will work with your current code, the second you need to create a method named money in the implicit class. – pedrofurla Nov 20 '13 at 19:07
  • @pedrofurla The first one does not work with the current code. For `100.EUR` the compiler says that `EUR` is not a member if `Int`. If `100.EUR` would work `100 EUR` would work too, because it the same expression in scala. – Erik Nov 20 '13 at 21:32
  • True, my bad, but you can create the methods `EUR` and `USD` in DoubleMoney. – pedrofurla Nov 20 '13 at 21:42
  • 1
    I do not believe this can be done. The currency must be a method to define the value as you `100 EUR`. You could always add methods for the 20-30 most important currencies. *But much more importantly is never use doubles to store Money* See "Item48: Avoid float and double if exact answers are required" in Effective Java. Try the following expression in the REPL `1.03 - .42`. See here for correct way to work with money http://www.javapractices.com/topic/TopicAction.do?Id=13 – iain Nov 22 '13 at 14:01

1 Answers1

0

As I have commented above I do not believe that this can be done without defining the currencies as methods. You can however use methods to define the Currency objects too. Then these methods can be defined in a trait to ensure that currencies and their conversions are kept in step.

Here is a small worksheet example:

object Money {
  import scala.math.BigDecimal

  /** Wrap a BigDecimal with a Currency - doubles and money do not play nicely */
  case class MoneyAmount(amount: BigDecimal, currency: Currencies.Type) {
    override def toString: String = s"${amount} ${currency.symbol}"
  }

  /** Trait that lists all the available currencies */
  trait Currencies {
    /** Abstract type provided by the implementations */
    type Type

    def EUR: Type
    def USD: Type
  }

  /** Object that holds all the available currencies as case class objects */
  object Currencies extends Currencies {
    case class Type(symbol: String, decimalPlaces: Int)

    override val EUR = Type("EUR", 2)
    override val USD = Type("USD", 2)
  }

  /** Implicit conversion from Int to money about */
  implicit class IntToMoneyAmount(value: Int) {
    type Type = MoneyAmount

    private def makeAmount(currency: Currencies.Type) = MoneyAmount(BigDecimal(value), currency)

    override def EUR = makeAmount(Currencies.EUR)
    override def USD = makeAmount(Currencies.USD)
  }

  5 EUR                                           //> res0: Money.MoneyAmount = 5 EUR
}

This is very verbose and can be achieved without the Currency trait but then you would risk adding a currency without adding a conversion for it.

iain
  • 10,798
  • 3
  • 37
  • 41