Pattern Matching of Units of Measure in F#

半世苍凉 提交于 2019-12-05 09:42:05

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.

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.

W Poe

See the Units at Runtime Section at http://msdn.microsoft.com/en-us/library/dd233243.aspx.

I agree with @kvb, I think the best way around this is to pass an object.

What I would like to do, using your code structure:

let convert (v: float<_>) =
  match v with
  | :? float<m> -> v<m>
  | :? float<inches> -> v * 2.54 / 100.0<m>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!