Avoiding Silly Mistakes with F# Unit of Measure
One of the most attractive things about F# is that it comes with built in protection against many of silly mistakes that as programmers we make all the time (it’s not just me right?). So for example, with F# you have to try really hard to get a NullReferenceException
, whilst in C#, well let’s just say it’s quite easy.
Another F# feature that can help us out is units of measure. As a simple example, I recently heard someone say on a podcast that he billed $100 an hour for his time, and I wondered what the equivalent annual salary was in British pounds.
Of course this is a simple calculation. Multiply the hourly rate by the number of billable hours per week times the number of weeks worked per year, and then multiply that by the exchange rate to turn it into pounds per year. So if we guess that he can bill 32 hours in a week and he works 47 weeks in a year, and if the exchange rate is 1.45 then we get the following calculation:
let dollarsPerHour = 100.0
let hoursBilledPerWeek = 32.0
let weeksWorkedPerYear = 47.0
let exchangeRate = 1.45
let poundsPerYear = dollarsPerHour * hoursBilledPerWeek * weeksWorkedPerYear * exchangeRate
Which works out at £218,080. Which is a lot! And also wrong. Because we made a silly mistake. Our exchange rate is in dollars per pound, not pounds per dollar, so we needed to divide by the exchange rate, not multiply.
So how could F# units of measure help us out here? Well, it’s really easy to declare arbitrary units of measure. We can create them for dollars and pounds, as well as hours, weeks and years like this:
[<Measure>] type dollar
[<Measure>] type pound
[<Measure>] type hour
[<Measure>] type week
[<Measure>] type year
So now when we declare our variables, we can specify what units they are in like this:
let hoursBilledPerWeek = 32.0<hour/week>
let weeksWorkedPerYear = 47.0<week/year>
let dollarsPerHour = 100.0<dollar/hour>
let exchangeRate = 1.45<dollar/pound>
Now this won’t prevent us from combining them in the wrong way as before
let poundsPerYear = dollarsPerHour * hoursBilledPerWeek * weeksWorkedPerYear * exchangeRate
But when we do so, the compiler is going to give us a very big hint that something is wrong. Here’s the output when we run this in F# interactive:
val poundsPerYear : float<dollar ^ 2/(pound year)> = 218080.0
What we’re being told here is that the answer to our calculation is 218080 square dollars per pound year! Clearly we’ve got something wrong. If we fix up our calculation:
let poundsPerYear = dollarsPerHour * hoursBilledPerWeek * weeksWorkedPerYear / exchangeRate
Then immediately we can see that we’re on the right track because our answer has the right units. It’s 102724 pounds per year:
val poundsPerYear : float<pound/year> = 103724.1379
And there are loads more good uses you can put this to. Often when writing games like tetris or snake games, I’ve needed to convert between grid coordinates and screen coordinates. By giving both their own units of measure, it becomes a lot harder to accidentally draw something in the wrong place or at the wrong size.
So do consider F# units of measure if you’re writing code containing calculations of any sort. By annotating each value with a type, it allows the compiler to stop us doing stupid things (e.g. you can’t add dollars to weeks), or at the very least as we saw above, give us a big warning through the type system that we’ve gone wrong somewhere.