2013-01-05 12 views
5

Czy można zdefiniować funkcję, która jest zarówno ogólna nad typem danych, jak i jednostką miary? Na przykład, co chciałbym zrobić, ale nie skompilować (choć nawet nie bez jednostki miary obecnej, ale wierzę, że oddaje to, co chciałbym zrobić):f # generyczne jednostki nad typami ogólnymi

let inline dropUnit (x : 'a<_>) = x :> typeof(a) 

idei o to, że zdefiniowałem niektóre jednostki miary, np "Kg" i "l" i dicriminated unia:

type Unit = 
    | Weight of float<kg> 
    | Volume of float <l> 

i chciałbym zrobić coś takiego:

let isValidUnitValue myUnit = 
    match myUnit with 
     | Weight(x) -> (dropUnit x) > 0. 
     | Volume(x) -> (dropUnit x) > 0. 

Zdaję sobie sprawę, że w tym konkretnym przypadku może po prostu użyć

let dropUnit (x : float<_>) = (float) x 

, ale zacząłem zastanawiać się nad ogólnym przypadkiem podczas pisania powyższego.

+0

Inną opcją jest użycie funkcji inline do podrabiania interfejsu. –

Odpowiedz

7

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) 
+0

Świetna odpowiedź, dzięki! – Bram

Powiązane problemy