0

It's easy enough to convert from a measurement unit to the underlying value type and back:

[<Measure>]
type meter

module Meter =
    let from m = m * 1</meter>
    let too m = m * 1<meter>

But then I thought, wouldn't it be lovely to have a conversion from one underlying type to the measurement unit and back, generically? Sure, I can on each instance be specific about it but I figured it'd be convenient to have a cast-to and cast-from operator, while retaining type safety.

My first attempt:

[<AutoOpen>]
module Convert = 
    let inline (!->) x = x * 1<_>    // (often) magically works
    let inline (!-<) x = x * 1</_>   // syntax error on the "/_"

The !-> operator works by virtue of type inference. It would be nice if the second operator also worked, but /_ is not supported syntax. I tried some type-annotated variants but couldn't come up with a straight generic solution, in part because we cannot write 'T<'M> or some variant thereof.

I then thought of using the trick used for implicit conversions, but then applied to explicit conversions, since the underlying type of 'T<'M> if 'T and an explicit cast always exists:

[<AutoOpen>]
module Convert = 
    let inline explicit_convert (x:^a) : ^b = ((^a or ^b) : (static member op_Explicit : ^a -> ^b) x) 
    let inline (::>) x = explicit_convert x       // works both ways

This has the added benefit that it works on any type that has an explicit conversion operator. Unfortunately, the compiler complains that it may not be safe to do so:

Member constraints with the name 'op_Explicit' are given special status by the F# compiler as certain .NET types are implicitly augmented with this member. This may result in runtime failures if you attempt to invoke the member constraint from your own code.

I'm not sure if I'm on the right track or whether there's a more type-safe way of creating an operator that can go from any measurement to the underlying type and back (including complex measurements, like int<meter ^ 4> or int<meter / sec ^ 2>)

Abel
  • 56,041
  • 24
  • 146
  • 247

2 Answers2

3

LanguagePrimitives.FloatWithMeasure, Int32WithMeasure, etc methods are provided in the runtime, which will generically "unitize" a value.

Similarly, float, int, etc functions (normally used for casting) will strip off a generic unit.

The tricky bit is seamlessly binding to the right functions at compile time in a general way. Below does what I think you want, following the approach shown here, which I believe is the standard trick for this kind of stuff.

[<AutoOpen>]
module Convert = 

  type AddUnits = AddUnits with
      static member ($) (AddUnits, x: float) = LanguagePrimitives.FloatWithMeasure<_>(x) 
      static member ($) (AddUnits, x: int) = LanguagePrimitives.Int32WithMeasure<_>(x)

  let inline (!->) x = AddUnits $ x

  type StripUnits = StripUnits with
      static member ($) (StripUnits, x:float<_>) = float x
      static member ($) (StripUnits, x:int<_>) = int x

  let inline (!-<) x = StripUnits $ x

open FSharp.Data.UnitSystems.SI.UnitSymbols

!-< 22<m>       // 22
!-< 9.8<m/s^2>  // 9.8

let x : int<m> = !-> 22         // 22<m>
let y : float<m/s^2> = !-> 9.8  // 9.8<m/s^2>
Community
  • 1
  • 1
latkin
  • 16,402
  • 1
  • 47
  • 62
  • Thanks, this looks promising. I'm unfamiliar with the `$` syntax, can you elaborate a little? Is that some auto mapping of the Du? – Abel Oct 31 '15 at 08:36
  • @Abel $ is just the choice latkin made for his custom operator (he could have chosen (+) instead if he wanted).He made that choice to mimic the "apply" operator in Haskell (see [here](http://stackoverflow.com/a/19521376/4925216)) – Sehnsucht Oct 31 '15 at 11:32
  • 2
    The first part was already in a [previous answer](http://stackoverflow.com/questions/28786971/generic-units-in-f/28787251#28787251). The fact that you suggested exactly the same code makes me think it definitely should be part of an F# library ;) – Gus Oct 31 '15 at 20:38
1

You have actually already written the function to remove the units of measure - see this example:

> let t:int = !->1<m>;;

val t : int = 1
John Palmer
  • 25,356
  • 3
  • 48
  • 67