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.
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.
Now select FSharp.PowerPack and click OK.
If you're working in FSharp Interactive, type
Now you'll have at your disposal the complete set of SI base and derived units, defined Microsoft.FSharp.Math.SI.
Also available are various physical constants, defined in Microsoft.FSharp.Math.PhysicalConstants.
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.
What if you need to convert between feet and metres? First, define a conversion factor.
What are the units of feetPerMetre? Answer: feet per metre (doh!), or ft/m for short. Now we can convert distances...
...and we can convert back the other way by multiplying instead of dividing:
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:
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":
Now we can just write ft.perMetre.
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:
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>:
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>
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?