Failure to understand or represent units has caused several major disasters, including the costly Ariane 5 disaster in 1996. This is one of those things that DSLs often get right, but mainstream programming languages just ignore. Or, worse, they implement a clunky unit of measure library that ensures you can never again write a sensible arithmetic expression.
While I was at JAOO Australia this week, Amanda Laucher showed some F# code for a recipe that caught my attention. It used numeric literals with that directly attached units to quantities. What’s more, it was intelligent about combining units.
I went looking for something similar in Scala. I googled my fingertips off, but without much luck, until Miles Sabin pointed out that there’s already a compiler plugin sitting right next to the core Scala code itself.
Installing Units
Scala has it’s own package manager, called sbaz. It can directly install the units extension:
sbaz install units
This will install it under your default managed installation. If you haven’t done anything else, that will be your Scala install directory. If you have done something else, you probably already know what you’re doing, so I won’t try to give you instructions.
Using Units
To use units, you first have to import the library’s “Preamble”. It’s also helpful to go ahead and import the “StandardUnits” object. That brings in a whole set of useful SI units.
I’m going to do all this from the Scala interactive interpreter.
scala> import units.Preamble._
import units.Preamble._
scala> import units.StandardUnits._
import units.StandardUnits._
After that, you can multiply any number by a unit to create a dimensional quantity:
scala> 20*m
res0: units.Measure = 20.0*m
scala> res0*res0
res1: units.Measure = 400.0*m*m
scala> Math.Pi*res0*res0
res2: units.Measure = 1256.6370614359173*m*m
Notice that when I multiplied a length (in meters) times itself, I got an area (square meters). To me, this is a really exciting thing about the units library. It can combine dimensions sensibly when you do math on them. In fact, it can help prevent you from incorrectly combining units.
scala> val length = 5*mm
length: units.Measure = 5.0*mm
scala> val weight = 12*g
weight: units.Measure = 12.0*g
scala> length + weight
units.IncompatibleUnits: Incompatible units: g and mm
I can’t add grams and millimeters, but I can multiply them.
Creating Units
The StandardUnits package includes a lot of common units relating to basic physics. It doesn’t have any relating to system capacity metrics, so I’d like to create some units for that.
scala> import units._
import units._
scala> val requests = SimpleDimension("requests")
requests: units.SimpleDimension = requests
scala> val req = SimpleUnit("req", requests, 1.0)
req: units.SimpleUnit = req
scala> val Kreq = SimpleUnit("Kreq", requests, 1000.0)
Kreq: units.SimpleUnit = Kreq
Now I can combine that simple dimension with others. If I want to express requests per second, I can just write it directly.
scala> 565*req/s
res4: units.Measure = 565.0*req/s
Conclusion
This extension will be the first thing I add to new projects from now on. The convenience of literals, with the extensibility of adding my own dimensions and units means I can easily keep units with all of my numbers.
There’s no longer any excuse to neglect your units in a mainstream programming language.