views:

183

answers:

2

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?

+2  A: 

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.

kvb
Hi,thanks for the answer. I will have to rethink my code once again.One comment:Why a value with units <Pa m s / kg> is not simplified into <1/s> when using Microsoft.FSharp.Math.SI module?
Oldrich Svec
@Oldrich - It's not clear to me why that's not simplified (my reading of the spec indicates that it should be), but F# is aware that those measures are equivalent (e.g. you can write `let (x:float<1/s>) = 1.0<Pa m s / kg>` and the compiler will allow it). It's possible that abbreviations such as Pa aren't always expanded out for display purposes.
kvb
+3  A: 

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.

Tomas Petricek