Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern Matching of Units of Measure in F#

This function:

let convert (v: float<_>) =
  match v with
  | :? float<m> -> v / 0.1<m>
  | :? float<m/s> -> v / 0.2<m/s>
  | _ -> failwith "unknown"

produces an error

The type 'float<'u>' does not have any proper subtypes and cannot be used as the source of a type test or runtime coercion.

Is there any way how to pattern match units of measure?

like image 886
Oldrich Svec Avatar asked Apr 19 '10 09:04

Oldrich Svec


2 Answers

As @kvb explains in detail, the problem is that units of measure are a part of the type. This means that float<m> is different type than float<m/s> (and unfortunately, this information isn't stored as part of the value at runtime).

So, you're actually trying to write a function that would work with two different types of input. The clean functional solution is to declare a discriminated union that can hold values of either the first type or the second type:

type SomeValue = 
  | M of float<m>
  | MPS of float<m/s>

Then you can write the function using ordinary pattern matching:

let convert v = 
  match v with 
  | M v -> v / 0.1<m>
  | MPS v -> v / 0.2<m/s>

You'll need to explicitly wrap the values into the discriminated union value, but it's probably the only way to do this directly (without making some larger changes in the program structure).

For normal types like int and float, you could also use overloaded members (declared in some F# type), but that doesn't work for units of measure, because the signature will be the same after the F# compiler erases the unit information.

like image 73
Tomas Petricek Avatar answered Sep 22 '22 02:09

Tomas Petricek


There are two problems with your approach. First of all, when you use an underscore in the definition of your function, that's the same as using a fresh type variable, so your definition is equivalent to the following:

let convert (v: float<'u>) = //'
  match v with
  | :? float<m> -> v / 0.1<m>
  | :? float<m/s> -> v / 0.2<m/s>
  | _ -> failwith "unknown"

What the error message is telling you is that the compiler know that v is of type float<'u>, and float<'u> has no proper subtypes, so there's no point in doing a type test to determine if it's a float<m> or any other type.

You might try to get around this by first boxing v into an object and then doing a type test. This would work, for instance, if you had a list<'a> and wanted to see if it were a list<int>, because full type information about generic objects is tracked at runtime including generic type parameters (notably, this is different from how some other runtimes like Java's work). Unfortunately, F# units of measure are erased at runtime, so this won't work here - there is no way for the system to infer the correct measure type given a boxed representation, since at runtime the value is just a plain float - F#'s system for units of measure is actually quite similar in this respect to how Java handles generic types.

As an aside, what you're trying to do seems quite suspect - functions which are generic in the unit of measure shouldn't do different things depending on what the measure type is; they should be properly parametric. What exactly are you trying to achieve? It certainly doesn't look like an operation which corresponds to physical reality, which is the basis for F#'s measure types.

like image 24
kvb Avatar answered Sep 20 '22 02:09

kvb