Is it possible to define a function that is both generic over data type and unit of measure? E.g., what I would like to do, but doesn't compile (though it wouldn't even without units of measure present, but I believe I conveys what I'd like to do):
let inline dropUnit (x : 'a<_>) = x :> typeof(a)
the idea here is that I've defined some units of measure, e.g. "kg" and "l" and a dicriminated union:
type Unit =
| Weight of float< kg >
| Volume of float < l >
and I would like to do something like:
let isValidUnitValue myUnit =
match myUnit with
| Weight(x) -> (dropUnit x) > 0.
| Volume(x) -> (dropUnit x) > 0.
I am aware that for this particular case I could just use
let dropUnit (x : float<_>) = (float) x
but I started wondering about the general case while writing the above.
For your specific question how to write your isValidUnitValue
function, the answer is:
let inline isValidUnitValue myUnit = myUnit > LanguagePrimitives.GenericZero
So you don't need to define a Discriminated Union.
Regarding the original question whether is it possible to define a function that is both generic over data type and unit of measure like dropUnit
the short answer is no. If such function exists it would have a signature like 'a<'b> -> 'a
and in order to represent it the type system should implement higher kinds.
However there are tricks using overload and inline:
type UnitDropper =
static member drop (x:sbyte<_> ) = sbyte x
static member drop (x:int16<_> ) = int16 x
static member drop (x:int<_> ) = int x
static member drop (x:int64<_> ) = int64 x
static member drop (x:decimal<_>) = decimal x
static member drop (x:float32<_>) = float32 x
static member drop (x:float<_> ) = float x
[<Measure>] type m
let x = UnitDropper.drop 2<m> + 3
But this is not really a generic function, you can't write something generic on top of it.
> let inline dropUnitAndAdd3 x = UnitDropper.drop x + 3 ;;
-> error FS0041: A unique overload for method 'drop' could not be determined ...
let inline retype (x:'a) : 'b = (# "" x : 'b #)
[<Measure>] type m
let x = retype 2<m> + 3
let inline dropUnitAndAdd3 x = retype x + 3
The problem is that retype
is too generic, it will allow you write:
let y = retype 2.0<m> + 3
Which compiles but will fail at run-time.
type DropUnit = DropUnit with
static member ($) (DropUnit, x:sbyte<_> ) = sbyte x
static member ($) (DropUnit, x:int16<_> ) = int16 x
static member ($) (DropUnit, x:int<_> ) = int x
static member ($) (DropUnit, x:int64<_> ) = int64 x
static member ($) (DropUnit, x:decimal<_>) = decimal x
static member ($) (DropUnit, x:float32<_>) = float32 x
static member ($) (DropUnit, x:float<_> ) = float x
let inline dropUnit x = DropUnit $ x
[<Measure>] type m
let x = dropUnit 2<m> + 3
let inline dropUnitAndAdd3 x = dropUnit x + 3
let y = dropUnit 2.0<m> + 3 //fails at compile-time
In the last line you'll get a compile-time error: FS0001: The type 'int' does not match the type 'float'
Another advantage of this approach is that you can extend it later with new types by defining a static member ($) in your type definition like this:
type MyNumericType<[<Measure 'U>]> =
...
static member dropUoM (x:MyNumericType<_>) : MyNumericType = ...
static member ($) (DropUnit, x:MyNumericType<_>) = MyNumericType.dropUoM(x)
let inline retype (x: 'T) : 'U = (# "" x: 'U #)
let inline stripUoM (x: '``Num<'M>``) =
let _ = x * (LanguagePrimitives.GenericOne : 'Num)
retype x :'Num
This is similar to 2) but it doesn't require a type annotation. The limitation is that it works for numeric types only, but normally that's the use case with UoMs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With