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.
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.



Comments
I had to make some tweaks to my sbaz install to download units.
My universe was set to lamp-rc rather than scala-dev. Units is available from scala-dev. To update it I went to the directory where my sbaz descriptor files are on my system:
I then set my universe to scala-dev:
Units was then listed as available.
Posted by: Marcel Molina
|
May 8, 2009 1:33 PM
Do you allow conversions for things like different temperature scales, and if so, how do you deal with deltas?
If I want to express something like (10°C + 5°C) I would be suprised to get 15°C instead of 288.15°K.
Is there some kind of way that the system infers that 5°C is a delta temperature instead of an absolute temperature, or some way to specify that?
Are units statically checked?
If so, how do I specify the type of an operation like Math.pow where it only makes sense to allow either:
1. A scalar raised to any scalar power: Math.pow(4, y)
2. A non-scalar raised to a constant scalar power such that the resulting powers are integral: Math.pos(2*m*m, .5) but not Math.pow(2*m*m, .25)
Can unit types be used generically, as in generic vector or matrix operations?
cheers,
mike
Posted by: Mike Samuel
|
May 8, 2009 3:32 PM
Mike,
First off, I want to be clear that this isn't my library. I wrote up this post because it's a cool library and I couldn't find any other mention of it.
As far as I can tell, units.StandardUnits only contains SI units. So, while it can convert from grams to milligrams to yottagrams (I'm not making that up!), there don't appear to be conversions between different scales for the same type of dimension.
Units are statically checked. To me, that's part of the coolest thing about this library. So, for example, I just made a quick file "unit_example.scala":
import units.Preamble._
import units.StandardUnits._
object UnitExample extends Application {
2*kg + 2*m
}
Adding meters and kilograms doesn't make sense, and sure enough, this file won't compile. I get "error: incompatible units (Mass vs. Length)" when I compile that code. Multiplying the quantities would be allowed.
Powers are a tougher issue. You can multiply a quantity times itself, and that will respect the unit. Passing a quantity to Math.pow() or Math.sqrt(), though, results in an error because Scala can't find a suitable overload of the method. (And, I guess implicit conversion fails too.)
-Mike
Posted by: Anonymous
|
May 8, 2009 11:39 PM
This looks interesting. Who is the actual author of this?
It is missing some units and more complex operations are't seen either, but it is an elegant approach.
I tried to add lenght and mass, but I only got a runtime exception in the Scala IDE.
Compared to e.g. JSR-275, the standard library for Measures and Units it has no compile time checking, but it is a little shorter in its notation.
Posted by: Werner Keil
|
December 7, 2009 4:44 PM