F# More On Units of Measure

In my last post we looked at how F# units of measure can help add an extra layer of safety to your code by enforcing the use of correct measurements.  We also saw how we can define relationships between units of measure to provide easy conversions and keep code intuitive.  Of course, there’s still plenty to talk about.

In this post we’ll look at increasing the power of units of measure by adding static members, using generic units of measure, and defining custom measure-aware types.  I recognize that I haven’t written anything about custom types or generics in F# yet so if you’re learning along with my posts I encourage you to read-up some of these concepts on MSDN before going any further.  Don’t worry though, I’ll be visiting those topics soon enough.

Static Members

Formulas are just one way we can define relationships between units of measure and they will often be more than sufficient.  Sometimes we need a little more power than formula expressions offer.  In some cases it may be more appropriate to define static members on the units of measure themselves.  Static members also have the advantage of keeping the logic with the measures they affect.

Static members on units of measure can be values or functions.  Naturally, the complexity of the measure definition varies according to the complexity of the member.  If we’re only defining a conversion factor on one of the related measures the definition will closely resemble a basic definition.  If we need conversion functions on both of the related measures then the definition will be much more complex with the types defined together so they can reference each other.  Let’s take a look at both.

Conversion Factor

Let’s say we have need to convert between inches and feet (I’m American, I’m allowed to use these units!).  We can easily define the measures to include a static conversion factor:

[<Measure>] type ft
[<Measure>] type inch = static member perFoot = 12.0<inch/ft>

Any code that needs to use to that conversion factor can simply refer to the static member by name:

> let inches = 1.0<ft> * inch.perFoot;;
val inches : float<inch> = 12.0

> let feet = 12.0<inch> / inch.perFoot;;
val feet : float<ft> = 1.0

Conversion Functions

When more logic than a conversion factor is required we can define a static function in a measure. To illustrate how complex measure definitions can get we’ll return to converting between inches and pixels by defining a conversion function on both the inch and px measures. In order for type inference to work we’ll need to define the measure types at the same time with the and keyword. The dpi measure will be defined independently but before the other two.

[<Measure>] type dpi
[<Measure>]
type inch =
  static member ToPixels (inches : float<inch>) (resolution : float<dpi>) =
    LanguagePrimitives.FloatWithMeasure<px> (float inches * float resolution)
and
  [<Measure>]
  px =
    static member ToInches (pixels : float<px>) (resolution : float<dpi>) =
      LanguagePrimitives.FloatWithMeasure<inch> (float pixels / float resolution)

There should be very little surprising in the conversion functions themselves. All they’re doing is stripping the units from the parameters, multiplying or dividing the floats and converting the resulting value to a measured float.

Just like with the conversion factor, any code that needs to convert can simply refer to the static member:

let resolution = 150.0<dpi>
let pixels = inch.ToPixels 8.0<inch> resolution
let inches = px.ToInches pixels resolution

Generic Units of Measure

Everything function we’ve looked at so far in this post and the last has relied on specific measure types. We’ve seen how measure-aware functions add an extra level of safety when working across multiple units of measure. We’ve also briefly discussed how many functions are not written to accept measured values as arguments so any units must be dropped. Although many functions that aren’t measure-aware are outside of our control, we can take advantage of generic units of measure in our code to maintain that extra level of safety.

To use generic units of measure we just need to alter the type annotation in the function signature a little bit by replacing the concrete measure with an underscore:

> let square (x : float<_>) = x * x
val square : float<'u> -> float<'u ^ 2>

As you can see, the type inference engine has changed the underscore to 'u ('u being F#’s way to denote generics) and identified the return value as float<'u ^ 2>. We can now call this with any float value and get a result in the proper units.

> let squaredInches = square 3.0<inch>;;
val squaredInches : float<inch ^ 2> = 9.0

> let squaredPixels = square 450.0<inch>;;
val squaredPixels : float<inch ^ 2> = 202500.0

> let squaredUnitless = square 9.0;;
val squaredUnitless : float = 81.0

What’s more is that once we have a function defined to use generic units of measure, the type inference engine can infer the type for other functions that consume it.

> let cube x = x * square x;;
val cube : float<'u> -> float<'u ^ 3>

Even though we didn’t give the compiler any hints about x in the cube function it was able to infer by virtue of calling the square function that it should accept float<'u> and return float<'u ^ 3>!

> let cubedInches = cube 3.0<inch>;;
val cubedInches : float<inch ^ 3> = 27.0

> let cubedPixels = cube 450.0<inch>;;
val cubedPixels : float<inch ^ 3> = 91125000.0

> let cubedUnitless = cube 9.0;;
val cubedUnitless : float = 729.0

Custom Measure-Aware Types

The last thing we’ll examine in regards to units of measure is defining custom measure-aware types. The way we make a custom type measure-aware is to include a measure parameter as part of the type’s generic type list.

Let’s consider a simple Point type. This type will include the standard X and Y coordinates and a function for calculating the distance between two points. One way we could define this type is as a measure-aware record type:

type Point< [<Measure>] 'u > = { X : float<'u>; Y : float<'u> } with
  member this.FindDistance other =
    let deltaX = other.X - this.X
    let deltaY = other.Y - this.Y
    sqrt ((deltaX * deltaX) + (deltaY * deltaY))

Because we’re actually referencing the measure type in our value definitions we need to be sure to give it a name in the generic type list rather than using underscore as we saw earlier. All other usages of the measure are actually inferred. With this type defined we can consume it like any other type:

> let point1 = { X = 10.0<inch>; Y = 10.0<inch> };;
val point1 : Point<inch> = {X = 10.0;
                            Y = 10.0;}

> let point2 = { X = 20.0<inch>; Y = 15.0<inch> };;
val point2 : Point<inch> = {X = 20.0;
                            Y = 15.0;}

> let distance = point1.FindDistance point2;;
val distance : float<inch> = 11.18033989

We’re not restricted to record types when making custom types measure aware either. We can define a measure-aware class almost as easily:

type Point< [<Measure>] 'u >(x : float<'u>, y : float<'u>) =
  member this.X = x
  member this.Y = y

  member this.FindDistance (other : Point<'u>) =
    let deltaX = other.X - this.X
    let deltaY = other.Y - this.Y
    sqrt ((deltaX * deltaX) + (deltaY * deltaY))

  override this.ToString() =
    sprintf "(%f, %f)" (float this.X) (float this.Y)

Consuming the class is naturally a bit more verbose as well:

> let point1 = Point<inch>(10.0<inch>, 10.0<inch>);;
val point1 : Point<inch> = (10.000000, 10.000000)

> let point2 = Point<inch>(20.0<inch>, 15.0<inch>);;
val point2 : Point<inch> = (20.000000, 15.000000)

> let distance = point1.FindDistance point2;;
val distance : float<inch> = 11.18033989

Wrapping Up

Over the last two posts we’ve taken a pretty comprehensive tour of F#’s units of measure and seen how powerful the feature is. Annotating values with measures truly helps ensure code correctness by enforcing that calculations are performed against the correct measurements.

We may have finished with units of measure but there’s still plenty more to explore in F#. Over the coming weeks I’ll be taking a close look into other topics like tuples, records, discriminated unions, and pattern matching among other things. Thanks for reading!

Advertisement

3 comments

    1. Thanks for the feedback.

      I agree about using pixel/inch and that’s what I would normally do but it was an easy way to illustrate a use for static members on measures. I probably should have picked a different example but I was trying to keep a consistent theme across both posts.

      In regards to whether multiplying by 1.0 is better than using LanguagePrimitives I’m still torn. The multiplication version is definitely more concise and I used it in my last post. The biggest reason I used LanguagePrimitives here is because that’s the method highlighted in Expert F# 3.0 (http://www.amazon.com/Expert-F-3-0-Apress/dp/1430246502) by Don Syme, et. al.

Comments are closed.