Just over a week ago I took the Eurostar over to Paris for NCrafts, a conference bringing together over 300 software craftsmen and craftswomen:
The event was held in a crypt and featured a good number of F# sessions:
Mathias Brandewinder gave an excellent closing talk on The T in TDD : tests, types, tales.
In this live coding session, Mathias took the multi-currency money example from Kent Beck’s seminal Test-Driven Development by Example book. First implementing a dollars class in C# driven by a unit test for quick feedback and then contrasting it with a similar implementation in F# using the REPL for immediate feedback.
Unit Test
The system needs to be able to multiply a price in dollars by a number of shares, so that 5 USD * 2 = 10 USD:
public class Tests
{
[Test]
public void five_dollars_times_two_should_equal_ten_dollars()
{
// arrange
var five = new Dollars(5);
// act
var result = five.Times(2);
// assert
Assert.AreEqual(new Dollars(10), result);
}
}
C# Dollars
Based on the test an immutable dollars class can be implemented:
public class Dollars
{
private readonly decimal _amount;
public Dollars(decimal value)
{
_amount = value;
}
public decimal Amount
{
get { return _amount; }
}
public Dollars Times(decimal multiplier)
{
return new Dollars(this._amount * multiplier);
}
}
The code now compiles, but the test fails!
C# Equality
The test fails because in C# class types use reference equality, so we must override Equals:
public class Dollars
{
private readonly decimal _amount;
public Dollars(decimal value)
{
_amount = value;
}
public decimal Amount
{
get { return _amount; }
}
public Dollars Times(decimal multiplier)
{
return new Dollars(this._amount * multiplier);
}
public override bool Equals(object obj)
{
var that = obj as Dollars;
return
that != null
? this.Amount == that.Amount
: false;
}
}
Note: at this point FXCop will also recommend that we implement GetHashCode as we’ve implemented Equals.
F# Dollars
In F#, the simplest thing that could possibly work is a measure type which gives compile time type safety:
[<Measure>] type usd
5.0M<usd> * 2.0M = 10.0M<usd>
We can also test it immediately in F# Interactive as above, or alternatively write a unit test as below:
let [<Test>] ``5 USD * 2 = 10 USD`` () =
Assert.AreEqual(10M<usd>, 5M<usd> * 2M)
Note: F# units of measure are erased at compile time meaning there’s no runtime performance penalty.
F# Money
For a report we’d probably want to encode money dynamically with a currency component. Below I’ve chosen an F# record type:
type Money = { Amount:decimal; Currency:string } with
member this.Times(multiplier) = { this with Amount = this.Amount * multiplier }
let USD amount = { Amount=amount; Currency="USD" }
USD 10M = (USD 5M).Times(2M)
This succeeds immediately as F# implements equality (and GetHashCode) by default for us on record types.
Unquote
As an aside, I find assertions over numerical types are more natural using the Unquote library which lets you assert equality using the equals operator, i.e.
let [<Test>] ``5 USD * 2 = 10 USD`` () =
test <@ (USD 5M).Times(2M) = USD 10M @>
Summary
When writing code we may seek quick feedback on our first implementations. In C# we’d typically write reflection based unit tests to get early feedback, in F# we may use F# interactive first for immediate feedback and later promote useful tests to reflection based tests that run as part of our continuous build and may help find regressions.
Also in this scenario implementing types in F# required a lot less boilerplate than the equivalent C# code.