Units of Measure in F# (FSharp)

F# (FSharp) Local Constants

The last few months, I talked about how the immutability of F# can make programming in .NET easier by reducing the occurrence of some common bugs. This month, I hope to talk about yet another bug reducing feature of F#. That feature is units of measure.

Sometimes people use computer languages for numerical or scientific computing. When this is done, there can sometimes be a challenge to keep the units straight. A common example that comes to my mind is the sine and cosine math functions.

In C++, if you wanted to compute the sine of 90.0 degrees you might be tempted to do something like this:

    
#include "math.h"
#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
	double const result = sin(90.0f); 
	return 0;
}
    
    

But if you run the above program and set a breakpoint on the return line, you might get a nasty surprise that the result isn’t equal to 1.0 as you would expect but that it is closer to 0.89. What happened? Well you passed in the wrong units to the sine function. If you look at a doc page (1) for the sin C++ function, you will see that that function requires the input value to have units of radians. If you were to pass in π/2.0 instead of 90.0, the answer would be more familiar.

You might say that this is common knowledge but if you happen to be working with alternate libraries there could be some confusion. For example, consider the Direct2D D2D1::Matrix3x2F::Rotation function described here (2). This C++ function also takes angles as input but in the case of this function the angles are expressed in degrees. So if some angle functions require radian inputs and others require degrees, how do you make sure you pass in the correct values? The easy way is to use a language that supports units of measure like F# does.

Now, let’s reconsider the above problem of computing the sine using F#. The code would look something like this:

    
    let sineOf90 = sin 90.0
    printf "%f" sineOf90
    
    

If you run the program, you will still notice that the built in sine function still does not throw an error when the user accidentally passes in 90.0 degrees instead of π/2.0. This results in the unexpected output of about 0.89 again. Fortunately, there is something we can do. We can define our own sine function and use units of measure to tell the user that she/he *must* pass in radians.

Below, is a code example where we create our own sine function that uses units of measure to require radian values at compile time. It then shows how the code will not compile if the user makes a mistake and accidentally passes in an angle of degrees when they should have used radians:

    
    [<Measure>] type radians;
    let sine (x : float<radians>) = sin x
    let sineOfNinety = sine 90.0
    
    

If you try to compile the code, you will get two errors:

c:\...\Program.fs(2,37): error FS0001: The type 'float<radians>' does not match the type 'float'

and

c:\...\Program.fs(3,25): error FS0001: This expression was expected to have type float<radians> but here has type float

The first error says that the built in sine function we are calling doesn't use units of measure while the second error says that we tried to pass in an ordinary float when we *should* have passed in a float with units of type radians. We can get rid of the first error by simply removing the units just before the built in sine call since it is no longer needed. We do that like so (3):

    
    [<Measure>] type radians;
    let sine (x : float<radians>) = sin (float x)
    let sineOfNinety = sine 90.0
    
    

We still get an error stating that you can’t pass a plain float without units into our new sine function. At this point we are forced to specify the units when calling the sine function and we know that we need to correct our error since 90.0 radians is of no significant value but π/2.0 radians is. Our corrected code then becomes:

    
    open System
    [<Measure>] type radians;
    let sine (x : float<radians>) = sin (float x)
    let sineOfPIOverTwo = sine ((Math.PI / 2.0)*1.0<radians>)
    printf "%f" sineOfPIOverTwo
    
    

Now consider a more significant example, suppose we have a triangleAngle function that returns the angle of the triangle in degrees and we want to unknowingly pass that value into our new sine function. If the triangleAngle function and our sine function were coded to *properly* use units of measure, then we might come up with code that looks like this:

    
    open System
    [<Measure>] type radians;
    [<Measure>] type degrees; 

    (* Takes an angle of radians and converts it to degrees *) 
    let toDegrees (x : float<radians>) = 
        ((float x) * 360.0 / 2.0 / Math.PI) * 1.0<degrees>

    (* Takes the x and y dimensions of a triangle and returns the inner
       angle or the arctangent in degrees *)
    let triangleAngle y x = toDegrees ((atan (y/x))*1.0<radians>)

    (* Computes the angle in degrees of a right triangle with two sides
       of equal length *) 
    let theAngle = triangleAngle 16.0 16.0

    let sine (x : float<radians>) = sin (float x)
    printf "%f\n" (sine theAngle)
    
    

Fortunately, the compiler comes to our rescue and tells us we are stupidly trying to pass an angle in degrees into our sine function that only accepts radians. The compiler has just kept us from doing something excessively stupid all because we were wise enough to use the units of measure feature built into F# :-)

Now, if you are guessing that the solution to this bug is to write a toRadians function, you are right! That’s all we have to do to get our program working correctly and we can verify that the sine of a right triangle with two sides of length 16.0 is the same as the sine of 45 degrees which is the same as the sine of π/4.

    
    open System
    [<Measure>] type radians;
    [<Measure>] type degrees; 

    (* Takes an angle of radians and converts it to degrees *) 
    let toDegrees (x : float<radians>) = 
        ((float x) * 360.0 / 2.0 / Math.PI) * 1.0<degrees>

    (* Takes an angle of degrees and converts it to radians *) 
    let toRadians (x : float<degrees>) = 
        ((float x) * 2.0 * Math.PI / 360.0) * 1.0<radians>

    (* Takes the x and y dimensions of a triangle and returns the inner
       angle or the arctangent in degrees *)
    let triangleAngle y x = toDegrees ((atan (y/x))*1.0<radians>)

    (* Computes the angle in degrees of a right triangle with two sides
       of equal length *) 
    let theAngle = triangleAngle 16.0 16.0

    let sine (x : float<radians>) = sin (float x)

    (* Notice how both values match
       The former converts 45 degrees to radians before calling sine
       and the later just simply users the radians equivalent of 45 
       degrees which is PI/4 *) 
    printf "%f\n" (sine (toRadians theAngle))
    printf "%f\n" (sine ((Math.PI / 4.0)*1.0<radians>))
    
    

It’s awesome that F# can catch these kinds of stupidities for us as long as we use its units of measure feature :o). I hope the feature comes to other languages like unmanaged C++ someday.

Bibliography

1. cplusplus.com
Documentation Page for C Sin Function
cplusplus.com. [Online]
[Cited: MAR 29, 2013.]
http://www.cplusplus.com/reference/cmath/sin/

2. Microsoft.
Matrix3x2F::Rotation method.
Windows Dev Center - Desktop. [Online: NOV 29, 2012]
[Cited: MAR 29, 2013.]
http://msdn.microsoft.com/en-us/library/windows/desktop/dd372285(v=vs.85).aspx

3. Microsoft.
Units of Measure (F#).
MSDN. [Online]
[Cited: MAR 29, 2013.]
http://msdn.microsoft.com/en-us/library/dd233243.aspx

©2013 - Shawn Eary
This post and all included code is released under the Free Christian Document License (FCDL)