Comparing Apples to Pears in Scala - or Abstract Types to the Rescue

Urs Peter

Abstract types in Scala can make your life much easier. In this blog I’m going to recap my intellectual journey to compare 'apples to pears' in a typesafe manner, which led me to abstract types.

My quest was to write code, which enables me to compare different kinds of currencies as elegant as possible. What I wanted was a very simple DSL with which I could do the following:

2.dollar > 1.euro

The very beginning

That’s where I started out:

trait Money extends Ordered[Money] {
   val unit:String
   val amount:Double
   def compare(that:Money) = if(amount > that.amount) 1 else -1
}

case class Euro(amount: Double) with Money {
    val unit = “EUR” 
}

case class Dollar(amount:Double) with Money {
    val unit = “USD” 
}

You probably see already which problem we’ll encounter. When we want to compare the same currencies this code will work fine. However, comparing two different kinds of currencies will compile but will yield a wrong result:

//works as expected
assert(Euro(200) > Euro(100))

//compiles but yields wrong result
assert(Euro(99) > Dollar(100))

So my question was how to cope with this situation. Having a trait Money that does the comparison is definitely not the way to go because it does not take the conversion of currencies into considering. This design allows me to compare Euros with Dollars whereas we’re only comparing amounts.

No real option for the problem

One second I considered to add the following check to the Money’s compare method:

def compare(that:Money) = {
    require(unit == that.unit)
    if(amount > that.amount) 1 else -1
}

Needless to say that this cannot be considered a viable solution. Especially with regard to Scala’s rich type system, which should help you to exactly solve such problems. So how to cope with it?

Abstract types to the rescue

Minutes later I remembered abstract types, which provided the solution I was exactly looking for. Instead of letting the compare method accept a type of Money, I define an abstract type alias called Currency, which extends from Money. This abstract type is then used as input for the compare method.

trait Money  {
  type Currency <: Money
  val unit: String
  val amount: Double
  def compare(that:Currency):Int = if(amount > that.amount) 1 else -1
} 

Next I can define an OrderedMoney trait in order to introduce the Ordered trait, for which the compare method is needed in the first place.

 
trait OrderedMoney[T <: Money] extends Money with Ordered[T] 

Because the super trait, Money, already contains an implementation of the compare method the OrderedMoney trait does not need to implement it. Obviously, we also could have provided the implementation of the compare method in the OrderedMoney trait. However the effect is the same.

The OrderedMoney trait needs to be mixed in for every concrete currency implementation as follows:

case class Euro(val amount: Double) extends OrderedMoney[Euro]{
  type Currency = Euro
  val unit = "EUR"
}

case class Dollar(val amount: Double) extends OrderedMoney[Dollar]{
  type Currency = Dollar
  val unit = "USD"
}

From this point on the compiler makes sure that I only compare apples to apples and not apples to pears:

//works as expected
assert(Euro(200) > Euro(100))

//does not compile anymore, as intended
assert(Dollar(100) > Euro(100))
scala: Diverging implicit conversion…

Making it compare apples to pears

In the end we would like to compare apples to pears or here Dollars to Euros. In order to achieve that we simply have to add some implicit conversion logic and our money DSL is (nearly) complete:

object Conversions {
  implicit def fromEuroToDollar(d: Euro) = new Dollar(d.amount * 1.2)
  implicit def fromDollarToEuro(d: Dollar) = new Euro(d.amount * 0.85)
}

For the sake of simplicity we use a hard-coded value to convert from one currency to another. Ideally such functionality would have to be placed in a separate class, like a CurrencyConverter. With these conversions in place apples can finally be compared to pears:

//works as expected
assert(Euro(200) > Euro(100))

//compiles AND works as expected
assert(Dollar(100) > Euro(100))

Finishing touch

For a user of this API it would be convenient to use natural syntax like 2.euro instead of Euro(2). For this to happen only another simple piece of conversion logic needs to be added:

object Conversions {
...
implicit def fromDoubleToCurrency(d: Double) = new {
    def euro =  Euro(d) 
    def dollar = Dollar(d)
  }
}

This implicit method converts a Double into an anonymous object that contains a euro and a dollar method. With this in place every Double is pimped with these methods, which makes this DSL as smooth to use as initially intended:

2.euro > 2.dollar

Round up: Adding basic arithmetic operations

Are we there yet? Well, we came quite far but it would be nice if we were able to calculate with currencies, preferably with different types of currencies. The following operations would be quite useful:

//calculate with same types of currencies
2.euro + 10.euro 

//calculations with different types of currencies
1.euro + 20.dollar – 5.pounds

The question is how to implement this requirement with as little impact as possible. With some lines of code this additional requirement can be satisfied quite easily:

trait Money  {
  ...
  def create(amount:Double):Currency
  def +(that:Currency) = create(amount + that.amount)
  def -(that:Currency) = create(amount - that.amount)
}

We've added a + and - method to Money and an abstract create method, which is needed to instantiate a currency with the calculated amount. For every type of currency the only thing we have to do is implementing the create method as follows:

case class Euro(val amount: Double) extends OrderedMoney[Euro]{
  type Currency = Euro
  val unit = "EUR"
  def create(amount:Double) = Euro(amount)
}

And from then on we not only can compare different currencies but also calculate with them:

//calculations and comparisons with different types of currencies
1.euro + 20.euro > 15.dollar – 3.euro

All at once

So to round up here the whole code, which enables you to compare apples to pears and perform some basic arithmetic operations all in a typesafe way:

trait Money  {
  type Currency <: Money
  val unit: String
  val amount: Double 
  def compare(that:Currency):Int =  if(amount > that.amount) 1 else -1
  def +(that:Currency) = create(amount + that.amount)
  def -(that:Currency) = create(amount - that.amount)
  protected def create(amount:Double):Currency 
}

trait OrderedMoney[T <: Money] extends Money with Ordered[T] 

case class Euro(amount: Double) extends OrderedMoney[Euro]{
  type Currency = Euro
  val unit = "EUR"
  def create(amount:Double) = Euro(amount)
}

case class Dollar(amount: Double) extends OrderedMoney[Dollar]{
  type Currency = Dollar
  val unit = "USD"
  def create(amount:Double) = Dollar(amount)
}

object Conversions {
  implicit def fromEuroToDollar(d: Euro) =  Dollar(d.amount * 1.2)
  implicit def fromDollarToEuro(d: Dollar) = Euro(d.amount * 0.85)
  implicit def fromDoubleToCurrency(d: Double) = new {
    def euro =  Euro(d) 
    def dollar = Dollar(d)
  }
}

//usage samples:
assert(2.dollar + 3.euro >= 1.dollar + 1.dollar + 1.euro)

After writing this code, I finally understood why Martin Odersky ‘s new company was baptized ‘typesafe’ : that’s what Scala for a 'big part' is 'all' about ;-)

Acknowledgments

This blog was partly inspired on an example in the book Programming in Scala, Chapter 20, Case study currencies

Comments (11)

  1. Andrew Phillips - Reply

    August 17, 2011 at 10:13 pm

    In the final code, what is the advantage of separating the "Money" and "OrderedMoney" traits?

    • Urs Peter - Reply

      August 22, 2011 at 7:07 pm

      Well, this is a good question and also one I asked myself many times.
      The reason why I chose for this solution is because it works, and
      I could not find another or more elegant way to make it work.
      Let's look at some alternatives:

      //does not work because Currency is not found by the compiler
      trait Money extends Ordered[Currency] {
      ...
      }

      //does not work because now the abstract type definition Currency expects a type for Money
      trait Money[T] extends Ordered[T] {
      type Currency <: Money[???]
      ...
      }

      That's it more or less. So that's the reason why I introduced the OrderedMoney
      class, which acts as a placeholder for the type the Ordered trait expects. Not really beautiful, other alternatives are welcome.

  2. Age Mooij - Reply

    August 17, 2011 at 10:16 pm

    Nice example !

  3. missingfaktor - Reply

    August 22, 2011 at 3:02 pm

    Nice write-up! Here is my take on the problem, without using subtyping: https://gist.github.com/1162307. Let me know what you think about this approach. Thanks!

  4. Andrew Phillips - Reply

    August 22, 2011 at 6:20 pm

    @missingfaktor: looks like you're using generics to pass the same information around.

    There's a good article by Bill Venners on abstracts vs. generics here: http://www.artima.com/weblogs/viewpost.jsp?thread=270195

    • Urs Peter - Reply

      August 22, 2011 at 7:17 pm

      Interesting article. I indeed pass the same type information around twice, once as generic and once as the implementation of a concrete type. However, in my example - compared to the one of Vill Venenrs - I do not have the choice to change the Ordered trait to use an abstract type even though my solution needs abstract types. Nice little dilemma, resulting in duplication of types. Any suggestion is welcome.

  5. slim ouertani - Reply

    August 24, 2011 at 9:33 am

    Thanks for sharing, very good article.

  6. Albert - Reply

    August 24, 2011 at 9:56 pm

    Nice writeup Urs! I was trying to accomplish the same thing with distances (km/miles) the other day.
    One question: What if you wanted to add x more currencies (pound and yen) for example. Wouldn't the number of implicit conversions you have to write explode if you want to be able to compare all of them with each other? Would it be possible to implicitly convert them to some standard (like euro) before comparing them?

  7. Forrest Butremovic - Reply

    August 30, 2011 at 6:06 pm

    I have recently been hoping to be able to do this to my own Laptop however I can't appear to make it happen. Do you know of a good computer area that can perform this for me?

  8. [...] Comparing Apples to Pears in Scala – or Abstract Types to the Rescue | Xebia Blog (tags: scala type) [...]

  9. turumbay - Reply

    March 2, 2012 at 1:05 am

    Great example, Urs! Thanks a lot!

    Can we extend this behaviour to any measurement entities?
    For example, is where any way to prevent duplication in Weight trait?
    (copy-paste-replace from yours code):
    trait Weight {
    type Unit that.amount) 1 else -1
    def +(that:Unit) = create(amount + that.amount)
    def -(that:Unit) = create(amount - that.amount)
    protected def create(amount:Double):Unit
    }

    trait OrderedWeight[T <: Weight] extends Weight with Ordered[T]

    case class Kg(amount: Double) extends OrderedWeight[Kg]{
    type Unit = Kg
    val unit = "kg"
    def create(amount:Double) = Kg(amount)
    }

    case class Pound(amount: Double) extends OrderedWeight[Pound]{
    type Unit = Pound
    val unit = "pound"
    def create(amount:Double) = Pound(amount)
    }

    object WeightConversions {
    implicit def fromKgToPound(d: Kg) = Pound(d.amount * 2.20462262)
    implicit def fromPoundToKg(d: Pound) = Kg(d.amount * 0.45359237)
    implicit def fromDoubleToWeight(d: Double) = new {
    def kg = Kg(d)
    def pound = Pound(d)
    }
    }

Add a Comment