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.
About Michael Nygard
Michael strives to raise the bar and ease the pain for developers across the country. He shares his passion and energy for improvement with everyone he meets, sometimes even with their permission. Michael has spent the better part of 20 years learning what it means to be a professional programmer who cares about art, quality, and craft. He's always ready to spend time with other developers who are fully engaged and devoted to their work--the "wide awake" developers. On the flip side, he cannot abide apathy or wasted potential.
Michael has been a professional programmer and architect for nearly 20 years. During that time, he has delivered running systems to the U. S. Government, the military, banking, finance, agriculture, and retail industries. More often than not, Michael has lived with the systems he built. This experience with the real world of operations changed his views about software architecture and development forever.
He worked through the birth and infancy of a Tier 1 retail site and has often served as "roving troubleshooter" for other online businesses. These experiences give him a unique perspective on building software for high performance and high reliability in the face of an actively hostile environment.
Most recently, Michael wrote "Release It! Design and Deploy Production-Ready Software", a book that realizes many of his thoughts about building software that does more than just pass QA, it survives the real world. Michael previously wrote numerous articles and editorials, spoke at Comdex, and co-authored one of the early Java books.More About Michael »
November 1 - 3, 2013
Current Topics on the NFJS Tour
- Core Java, JEE
- Dynamic Languages: Groovy, JRuby, Scala, Clojure
- RESTful Web Apps
- Frameworks: Hibernate, Grails, Spring, JSF, GWT, more
- Test Driven Design
- Ajax, Flex, RIA
Why Attend the NFJS Tour?
- » Cutting-Edge Technologies
- » Agile Practices
- » Peer Exchange
- Languages on the JVM: Scala, Groovy, Clojure
- Enterprise Java
- Core Java, Java 7
- Testing: Geb, Spock, Easyb
- NoSQL: MongoDB, Cassandra
- Spring 3
- Automation Tools: Git, Hudson, Sonar
- HTML5, Ajax, jQuery, Usability
- Mobile Applications - iPhone and Android
NFJS, the MagazineMay Issue Now Available
On the road to learningby Raju Gandhi
Refactoring to Modularityby Kirk Knoernschild
RESTful Groovyby Kenneth Kousen
Getting Started with D3.jsby Brian Sletten