Units of Measure in F#: Part Four, Parameterized Types
It's been a while since I wrote anything on this blog. Now is a good moment to continue, with last week's Beta 1 release of Visual Studio 2010, including F#, and a refresh to the F# Community Tech Preview add-on for VS 2008. If you haven't yet installed either of these, I recommend that you do. There have been many bug fixes and improvements to F# since last year.
Since the initial CTP release of F# I've been busy fixing some bugs that have been uncovered, and giving talks on the units feature. I presented at the ML workshop that was co-located with ICFP'08, the International Conference on Functional Programming. (For the theoretically minded, I also gave a talk at the Workshop on Mechanizing Metatheory, or WMM for short.) A couple of weeks ago I presented a lecture course on units-of-measure to PhD and masters students, at the Central European summer school on Functional Programming, CEFP'09. The lecture notes from that course will be peer-reviewed and published by Springer later this year.
Now back to the blog tutorial. In the first three parts of this series, we saw how to define units-of-measure, how to use units with the built-in floating-point type float, and how to write code that's generic in units-of-measure.
There are two other built-in types with this feature: float32 (the type of single-precision floating point) and decimal. Here's how constants of these types are introduced:
But what if I want to create my own types with units, for example, complex numbers, 3-vectors, or matrices? That's easy: just parameterize the type, just as you might parameterize a type such as list or Dictionary, but mark the parameter with the [<Measure>] attribute:
As you can see, units beget units: in the Sphere type, we've used the Vector3 type, which in turn used the built-in float type. All are parameterized on units-of-measure.
Now we can create a point and acceleration, both vectors, but with different units:
That was easy enough. Now let's define some operations on vectors:
Notice how F# inferred a generic type for dot product. Of course, we'd really like to extend operators such + and - with overloads for our new type. To do this, we can define static members on the type:
There are a couple of things to notice here. First, we've given the units of the arguments explicitly. If we don't do this, then F# will infer extra unit parameters rather than using the unit parameter 'u from Vector3 itself. Second, we have implemented various kinds of multiplication: multiplying a vector on the left and on the right by a scalar (i.e. a float), multiplying a vector by a vector to get the vector product, also known as the cross product, and finally, the dot product, which we've called '.*'. In order to define multiple overloads for *, we've used the OverloadID attribute to distinguish them. Now let's look at the signature that F# gives to this type, by hovering the cursor over the declaration:
We can now implement a "vector" version of Newton's second law (F = ma), multiplying a scalar mass by an acceleration vector to get a force vector:
The use of records in the Vector3 type is a traditional functional programming approach. A more .NET-oriented style of definition is to define a class with implicit constructor, instead of a record type. Here's a type for complex numbers defined this way, including instance members to obtain the polar representation of complex numbers as magnitude and phase, and to print complex numbers in the form a + ib:
One "gotcha" here is the need for explicit type signatures on the re and im properties. Without these hints, F# infers weaker types for arithmetic (try it!). Fortunately, this issue will be resolved in the final release of F#.
We can now do some electrics!
Executing this code in F# Interactive produces:
The amplitude of a voltage value has type float<V>, whilst the phase has type float, being measured in radians.
Summing up. We've seen how to parameterize user-defined types on units-of-measure, how to define members that make use of the unit parameters, and how to overload operators to define non-standard arithmetic.
Next time we'll have a look at the internals of type checking and type inference for units-of-measure. You don't need to know this to use the units feature, but you might be curious anyhow. For instance, however does F# figure out the type of the following?
Comments
- Anonymous
August 10, 2009
You are missing tags "Units of Measure" and "Units" on this last installment. These tags would make it convenient to just bookmark the RSS feed for the "Units of Measure" tag.I've always wanted .NET proper to support units. The curl runtime has had it forever.