In the first two articles in this series, we saw how to declare base and derived units, introduce constants with units, define our own conversion functions, and have F# check the units for us.
But what if we’re writing code that doesn’t care what units it is working in? That’s the subject of today’s article.
Let’s start simple. What is the type of fun x -> x*x? Well, multiplication is overloaded, so F# defaults to integers:
But if we annotate the argument, we can define squaring for all kinds of floats (hovering over the last of these to show its type):
This looks painful! What if we don’t tell F# what the units are, using the underscore notation to say “find me its units”?
F# has inferred a generic unit type for squaring. The notation ‘u looks like a type parameter, but is in fact a unit-of-measure parameter that can be instantiated at any units. Let’s use it on masses, lengths and speeds:
F# can infer generic unit-of-measure types with type annotations required only to resolve overloading. Here are some simple examples, tested out in F# Interactive:
Here’s one that requires a bit of head-scratching to understand:
Returning to reality, suppose we want to sum the elements of a list. In true functional style, we use one of the fold operators.
Oops – we don’t seem to have a nice generic type! The reason is simple: unless units are specified, constants, including zero, are assumed to have no units, i.e. to be dimensionless. So instead, let’s give 0.0 some units, but not tell F# what they are, by writing 0.0<_>:
Now we can go wild and write all sorts of statistical stuff.
And here are the beautiful types that are inferred for these functions:
Summing up: we’ve seen how to write code that is generic (a.k.a. as polymorphic) in units-of-measure.
Next time, we’ll write types that are generic in units-of-measure. This makes the feature extensible: if floats with units aren’t what you want, just define your own types!