Dla Państwa konkretne pytanie jak napisać funkcję isValidUnitValue
, odpowiedź brzmi:
let inline isValidUnitValue myUnit = myUnit > LanguagePrimitives.GenericZero
Więc nie trzeba zdefiniować Unii dyskryminowanych.
Odnośnie pierwotnego pytania, czy możliwe jest zdefiniowanie funkcji, która jest zarówno ogólna nad typem danych, jak i jednostką miary, taką jak dropUnit
, krótka odpowiedź brzmi "nie". Jeśli taka funkcja istnieje, będzie miała podpis taki jak 'a<'b> -> 'a
i aby go reprezentować, system typów powinien implementować wyższe rodzaje.
Jednak istnieją sztuczki wykorzystujące przeciążenie i inline:
1) Korzystanie przeciążeń (a la C#)
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
Ale to nie jest tak naprawdę funkcją rodzajowy, nie można napisać coś rodzajowe na do góry.
> let inline dropUnitAndAdd3 x = UnitDropper.drop x + 3 ;;
-> error FS0041: A unique overload for method 'drop' could not be determined ...
2) Korzystanie inline, wspólna sztuczka jest przepisywanie:
let inline retype (x:'a) : 'b = (# "" x : 'b #)
[<Measure>] type m
let x = retype 2<m> + 3
let inline dropUnitAndAdd3 x = retype x + 3
Problemem jest to, że retype
jest zbyt ogólny, pozwoli Ci napisać:
let y = retype 2.0<m> + 3
Który kompiluje, ale zawiedzie w czasie wykonywania.
3) Obiema przeciążeń i inline: ten trik rozwiąże tych dwóch kwestii stosowania przez przeciążenia typu pośredniego, w ten sposób można uzyskać zarówno w czasie kompilacji kontrole i będziesz w stanie określić funkcje generyczne :
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
W ostatnim wierszu otrzymasz błąd kompilacji: FS0001: The type 'int' does not match the type 'float'
Kolejną zaletą tego podejścia jest to, że można je rozszerzyć o nowe typy później poprzez zdefiniowanie statycznego mnie mber ($) w definicji typu:
type MyNumericType<[<Measure 'U>]> =
...
static member dropUoM (x:MyNumericType<_>) : MyNumericType = ...
static member ($) (DropUnit, x:MyNumericType<_>) = MyNumericType.dropUoM(x)
Inną opcją jest użycie funkcji inline do podrabiania interfejsu. –