Units of Measure in Scala
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.
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.
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.
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
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.