Blog

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

17 Aug, 2011
Xebia Background Header Wave

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:
[scala]2.dollar > 1.euro[/scala]

The very beginning

That’s where I started out:
[scala]
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”
}
[/scala]
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:
[scala]
//works as expected
assert(Euro(200) > Euro(100))
//compiles but yields wrong result
assert(Euro(99) > Dollar(100))
[/scala]
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:
[scala]def compare(that:Money) = {
require(unit == that.unit)
if(amount > that.amount) 1 else -1
}[/scala]
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.
[scala]trait Money {
type Currency <: Money
val unit: String
val amount: Double
def compare(that:Currency):Int = if(amount > that.amount) 1 else -1
} [/scala]
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.
[scala]
trait OrderedMoney[T <: Money] extends Money with Ordered[T]
[/scala]
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:
[scala]
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"
}
[/scala]
From this point on the compiler makes sure that I only compare apples to apples and not apples to pears:
[scala]
//works as expected
assert(Euro(200) > Euro(100))
//does not compile anymore, as intended
assert(Dollar(100) > Euro(100))
scala: Diverging implicit conversion…[/scala]

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:
[scala]object Conversions {
implicit def fromEuroToDollar(d: Euro) = new Dollar(d.amount 1.2)
implicit def fromDollarToEuro(d: Dollar) = new Euro(d.amount
0.85)
}[/scala]
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:
[scala]//works as expected
assert(Euro(200) > Euro(100))
//compiles AND works as expected
assert(Dollar(100) > Euro(100))[/scala]

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:
[scala]object Conversions {

implicit def fromDoubleToCurrency(d: Double) = new {
def euro = Euro(d)
def dollar = Dollar(d)
}
}[/scala]
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:
[scala]2.euro > 2.dollar[/scala]

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:
[scala]//calculate with same types of currencies
2.euro + 10.euro
//calculations with different types of currencies
1.euro + 20.dollar – 5.pounds[/scala]
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:
[scala]
trait Money {

def create(amount:Double):Currency
def +(that:Currency) = create(amount + that.amount)
def -(that:Currency) = create(amount – that.amount)
}[/scala]
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:
[scala]case class Euro(val amount: Double) extends OrderedMoney[Euro]{
type Currency = Euro
val unit = "EUR"
def create(amount:Double) = Euro(amount)
}[/scala]
And from then on we not only can compare different currencies but also calculate with them:
[scala]//calculations and comparisons with different types of currencies
1.euro + 20.euro > 15.dollar – 3.euro[/scala]

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:
[scala]
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)
[/scala]
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

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts