Units of measure
Type safety for numerics
Last updated
Was this helpful?
Type safety for numerics
Last updated
Was this helpful?
As we mentioned , F# has a very cool feature which allows you to add extra unit-of-measure information to as metadata to numeric types.
The F# compiler will then make sure that only numerics with the same unit-of-measure can be combined. This can be very useful to stop accidental mismatches and to make your code safer.
A unit of measure definition consists of the attribute [<Measure>]
, followed by the type
keyword and then a name. For example:
Often you will see the whole definition written on one line instead:
Once you have a definition, you can associate a measure type with a numeric type by using angle brackets with measure name inside:
You can even combine measures within the angle brackets to create compound measures:
If you use certain combinations of units a lot, you can define a derived measure and use that instead.
If you are using the units-of-measure for physics or other scientific applications, you will definitely want to use the SI units and related constants. You don't need to define all these yourself! These are predefined for you and available as follows:
The units-of-measure are just like proper types; you get static checking and type inference.
And of course, when using them, the type checking is strict:
If you want to be explicit in specifying a unit-of-measure type annotation, you can do so in the usual way. The numeric type must have angle brackets with the unit-of-measure.
The compiler understands how units of measure transform when individual values are multiplied or divided.
For example, in the following, the speed
value has been automatically given the measure <m/sec>
.
Look at the types of the acceleration
and force
values above to see other examples of how this works.
A numeric value without any specific unit of measure is called dimensionless. If you want to be explicit that a value is dimensionless, you can use the measure called 1
.
Note that you cannot add a dimensionless value to a value with a unit of measure, but you can multiply or divide by dimensionless values.
But see the section on "generics" below for an alternative approach.
What if you need to convert between units?
It's straightforward. You first need to define a conversion value that uses both units, and then multiply the source value by the conversion factor.
Here's an example with feet and inches:
And here's an example with temperature:
The compiler correctly inferred the signature of the conversion function.
Note that the constant 32.0<degF>
was explicitly annotated with the degF
so that the result would be in degF
as well. If you leave off this annotation, the result is a plain float, and the function signature changes to something much stranger! Try it and see:
To convert from a dimensionless numeric value to a value with a measure type, just multiply it by one, but with the one annotated with the appropriate unit.
And to convert the other way, either divide by one, or multiply with the inverse unit.
The above methods are type safe, and will cause errors if you try to convert the wrong type.
If you don't care about type checking, you can do the conversion with the standard casting functions instead:
Often, we want to write functions that will work with any value, no matter what unit of measure is associated with it.
For example, here is our old friend square
. But when we try to use it with a unit of measure, we get an error.
What can we do? We don't want to specify a particular unit of measure, but on the other hand we must specify something, because the simple definition above doesn't work.
The answer is to use generic units of measure, indicated with an underscore where the measure name normally is.
Now the square
function works as desired, and you can see that the function signature has used the letter 'u
to indicate a generic unit of measure. And also note that the compiler has inferred that the return value is of type "unit squared".
Indeed, you can specify the generic type using letters as well if you like:
You may need to use letters sometimes to explicitly indicate that the units are the same:
You cannot always use a measure directly. For example, you cannot define a list of feet directly:
Instead, you have to use the "multiply by one" trick mentioned above:
Multiplication by constants is OK (as we saw above), but if you try to do addition, you will get an error.
The fix is to add a generic type to the constant, like this:
A similar situation occurs when passing in constants to a higher order function such as fold
.
There are some cases where type inference fails us. For example, let's try to create a simple add1
function that uses units.
The warning message has the clue. The input parameter n
has no measure, so the measure for 1<_>
will always be ignored. The add1
function does not have a unit of measure so when you try to call it with a value that does have a measure, you get an error.
So maybe the solution is to explicitly annotate the measure type, like this:
But no, you get the same warning FS0064 again.
Maybe we can replace the underscore with something more explicit such as 1.0<'u>
?
But this time we get a compiler error!
The answer is to use one of the helpful utility functions in the LanguagePrimitives module: FloatWithMeasure
, Int32WithMeasure
, etc.
And for generic ints, you can use the same approach:
That takes care of functions. What about when we need to use a unit of measure in a type definition?
Say we want to define a generic coordinate record that works with an unit of measure. Let's start with a naive approach:
That didn't work, so what about adding the measure as a type parameter:
That didn't work either, but the error message tells us what to do. Here is the final, correct version, using the Measure
attribute:
In some cases, you might need to define more than one measure. In the following example, the currency exchange rate is defined as the ratio of two currencies, and so needs two generic measures to be defined.
And of course, you can mix regular generic types with unit of measure types.
For example, a product price might consist of a generic product type, plus a price with a currency:
An issue that you may run into is that units of measure are not part of the .NET type system.
F# does stores extra metadata about them in the assembly, but this metadata is only understood by F#.
This means that there is no (easy) way at runtime to determine what unit of measure a value has, nor any way to dynamically assign a unit of measure at runtime.
It also means that there is no way to expose units of measure as part of a public API to another .NET language (except other F# assemblies).
In F# 3.0 and higher (which shipped with Visual Studio 2012), these are built into the core F# libraries in the Microsoft.FSharp.Data.UnitSystems.SI
namespace (see the ).
In F# 2.0 (which shipped with Visual Studio 2010), you will have to install the F# powerpack to get them. (The F# powerpack is on Codeplex at ).