Units of Measure in F#: Part Two, Unit Conversions


In today’s article I’ll show you how to use different systems of units, convert between units, and interface with non-unit-aware code.


PowerPack goodies


First, though, let’s have a look at some handy definitions provided in the F# PowerPack. To access this DLL, you’ll need to reference if from your project. If you’re working in Visual Studio, right-click on References in the Solution Explorer window.


image


Now select FSharp.PowerPack and click OK.


image


If you’re working in FSharp Interactive, type


image


Now you’ll have at your disposal the complete set of SI base and derived units, defined Microsoft.FSharp.Math.SI.


image


Also available are various physical constants, defined in Microsoft.FSharp.Math.PhysicalConstants.


image 


Multiple unit systems


So for physicists, at least, there is no excuse to go non-metric. But what if you insist? No problem! Here is the example from Part One, using feet instead of metres as the unit of length.


image 


What if you need to convert between feet and metres? First, define a conversion factor.


image


What are the units of feetPerMetre? Answer: feet per metre (doh!), or ft/m for short. Now we can convert distances…

image


…and speeds…


image


…and we can convert back the other way by multiplying instead of dividing:


image


As far as F# is concerned, ft and m have nothing to do with each other. It’s up to you, the programmer, to define appropriate conversion factors. But the presence of units on the conversion factors makes mistakes much less likely. For example, what happens if I divide instead of multiply above? The type of the result suggests that something is awry, and will probably lead to a compile-time error later in the code:


image


It’s probably a good idea to package up conversion factors with the unit-of-measure to which they relate. A convenient way to do this is to add a static member to the unit-of-measure “type”:


image


Now we can just write ft.perMetre.


Interfacing non-unit-aware code


In Part One, we saw how to use syntax such as 2.0<s> to introduce units-of-measure into the types of floating-point values. But what if a quantity is stored in a file, or entered by the user through a GUI, or in a web form? In that case it’ll probably start out life as a string, to be parsed and converted into a float. How can we convert a vanilla float into, say, a float<s>? Easy: just multiply by 1.0<s>! Here’s an example:


image


If we want to convert back to a vanilla float, say, to pass to a non-unit-aware .NET method, we just divide by 1.0<s>:


image


Dimensionless quantities


But hang on a minute – what’s going on with that last example? The variable timeInSeconds has type float<s>, and we divided it by 1.0<s> which has type float<s>. So the units cancel out, producing units which we write simply as the digit 1. Hence the type of timeInSeconds / 1.0<s> is float<1>. Such a quantity is called dimensionless. Conveniently, F# defines the ordinary float type to be an alias for float<1>,using the definition



type float = float<1>


which makes use of overloading on the arity (= number of parameters) of the type. (Overloading is used to similar good effect with the non-generic .NET type System.Collections.IEnumerable and its generic variant System.Collections.Generic.IEnumerable). 


Summing up, we’ve learnt about unit conversions of various kinds, between different unit systems, and between ordinary floats and floats-with-units.


Next time we’ll look at generic unit types, or: what is a good type for fun x -> x*x?

Comments (21)

  1. alexey_r says:

    A great feature!

    How do unitful quantities look to the CLR? Something like

    [Measure]

    struct Second : IMeasure {}

    and

    [Measure]

    struct Mult<M1,M2> : IMeasure where M1 : IMeasure where M2 : IMeasure {}

    ?

  2. wil2200 says:

    Great post. However, I am having issues running the "gravitionalForce" portion of it.

    I keep getting an error saying that "error FS0039: The namespace or module ‘SI’ is not defined." even though I referenced it in both ways (I also have powerpack added to the project):

    open SI and/or

    open Microsoft.FSharp.Math.SI

    any thoughts on this?

    Thanks

    ~sparky

  3. andrewkennedy says:

    alexey_r:

    Units actually don’t get seen at all by the CLR – they are "erased". They are a purely static phenomenon, which means that the performance of code with units is no worse than the performance of the code with the units removed. You can think of them as a "refinement" of usual .NET types, seen only by F#. Of course this has some downsides (no runtime inspection of units) but our experience so far has been that they are in any case extremely useful in practice. And interestingly, all the "classical" functional languages (Haskell, Caml, OCaml, Standard ML) have purely static type systems.

    sparky: did you include

    open Microsoft.FSharp.Math

    ? This has PhysicalConstants as a submodule.

    • Andrew.
  4. WillSmith says:

    Would be nice to have unit of measure support as a general class library in the .net framework.  Can you specify the scale and precision as well?

  5. aatreya says:

    Andrew,

    How can I get the following to compile?:

    [<Measure>]

    type km =

       static member toM = 1.0/1000.0<m/km>

    [<Measure>]

    type m =

       static member toKm = 1000.0<km/m>

    Seems like there’s a fundamental limitation I’m running up against… or hopefully I’m just missing something.  The definition of m requires the definition of km, and vice versa.

  6. andrewkennedy says:

    You can define mutually recursive measures using "and" to connect them and placing the Measure attribute immediately before the name of the measure:

    type [<Measure>] km =

     static member toM = 1.0/1000.0<m/km>

    and [<Measure>] m =

     static member toKm = 1000.0<km/m>

  7. aatreya says:

    Wonderful – thanks!  I’m so excited that a practical programming language finally has units-of-measure capability.  Nice work.

  8. sparky00 says:

    This is incredibly annoying.

    [1] Reference FSharp Powerpack.dll – check

    [2] open Microsoft.FSharp.Math – Alt+Enter -> FSI pass

    [3] open SI – Alt+Enter -> FSI FAIL [error FS0039: The namespace or module ‘SI’ is not defined.]

    doing something as simple as:

    #light

    open Microsoft.FSharp.Math

    open SI

    let distance (d:float<m>) = d

    does not work -> anyone else has this problem?

    The VS IntelliSense and the documentation says that the SI module is installed, yet when I open and try to use it, nothing works.

    It seems to me that the problem is with the FSI and interpreting namespaces/modules but if anyone can give me some insights, that will really help!

  9. int19h says:

    As far as F# is concerned, ft and m have nothing to do with each other. It’s up to you, the programmer, to define appropriate conversion factors.

    This is mildly annoying. There’s no reason why feet cannot be converted to meters and back automatically (it’s not a narrowing conversion, so no opportunity for data loss), and even less for not being able to convert between m and km. It would seem to me that boost::units approach, which differentiates between abstract measures (duration, distance etc), and specific units of measurement, is more convenient.

  10. davidacoder says:

    This whole thing is seriously cool. Just today we stubmled again with unit mix up in our simulation code.

    But then, my most urgent question would be whether there are any plans to support this in other languages as well? F# is nice, but we have a large investment in C# and really wouldn’t want to move to F# just for the unit stuff… Any ideas on that?

  11. sparky00 says:

    I finally got this to work and I realized where the problem was. I was using the default F# project and .fs file to type up all the code in but when I Alt+Enter and send to FSI, that is where the problem started.

    Even though I added the reference via "Add Reference", I still get the error because FSI is not aware of project settings and all that. So in addition, I had to do #r "FSharp.PowerPack.dll" in the FSI window first, then Alt+Enter the rest of the code to it. Works now.

    Thanks for the help.

    ~sparky

  12. int19h says:

    It would seem to be that Microsoft is deliberately pushing for F# for scientific computing now, while C# is your typical "general-purpose" (read: system & LOB) language. C# 4.0 is expected to have 4 new language features, one of them generic variance – I wouldn’t expect to see units among the other three. Nor anytime soon – DbC, for example, would probably be higher-priority for C#.

  13. yemi says:

    Hello,

    One thing that I stumbled upon. The value conversion on the types get masked when you set a literal with the same name as a type. In FSI:

    [<Measure>] type km = static member pM = 1000.0<km/m>;;

    [<Measure>] type m = static member pK = 0.001<m/km>;;

    let km = 1.0<km>;;

    km;;

    m.pK;;

    km.pM;;

    The last line errors saying it can’t find pM. Good day.

    Yemi Bedu

  14. michen says:

    Does F# support units with different zero values, e.g. Kelvin and Celsius? What I mean is that conversion of 100C to K depends on whether 100C in this case means temperature of boiling water (then the result is 373.15K) or difference between boiling water temperature and freezing water temperature (in this case the answer is 100K).

    One can write two converters (say C.AbsToK and C.DeltaToK), but then it is user’s responsibility to do it right – F# will do no checking.

    Or maybe one should define two types of K and C units – absolute and delta, with rules like AbsX-AbsX=DeltaX, AbsX+DeltaX=AbsX, AbsX+AbsX=illegal, and so on?

  15. andrewkennedy says:

    Good point regarding units with different zero values. The units supported by F# are those for which multiplication makes sense, and Celsius doesn’t fit this model. It makes no sense to double an absolute temperature measured in degrees Celsius. Whereas for Kelvin, this is fine.

    A similar situation arises with absolute times versus time periods – it would indeed be nice to permit subtraction of absolute times, and rule out addition of absolute times, but permit either operation on time periods. One can do this by defining separate types, a little like DateTime and TimeSpan from the .NET framework.

  16. First, let me remind you that in my new ongoing quest to read source code to be a better developer ,

  17. Anders Cui says:

    NASA气象卫星意外坠落说明,计量单位绝非小事。为编程语言添加对计量单位的支持可以很大程度上避免这样的错误,编程任务也变得更有趣。F#提供了对计量单位的静态检查,并且封装了国际单位制的各个单位和物理常量,另外我们也可以定义自己的单位;在单位之间进行换算也很简单;此外F#还支持计量单位的泛型。作为对NASA气象卫星的纪念,本文最后给出了一个模拟太阳系的例子 🙂

  18. Kean over at AutoDesk (think AutoCAD etc.) is running an F# programming contest ! I’ve included his post

  19. Ostatnio ponowiłem wysiłki do opanowania nowych języków z rodziny MS w tym także implementacji znanych

  20. Ostatnio ponowiłem wysiłki do opanowania nowych języków z rodziny MS w tym także implementacji znanych

  21. Michael says:

    It's been a little while and there have been several developments in and around C# and F# since this time. Where might one find the PowerPack, particularly concerning units, measures, conversions, etc? Thank you…