2013-01-05 12 views
5

È possibile definire una funzione che è sia generica rispetto a tipo di dati e unità di misura? Ad esempio, quello che mi piacerebbe fare, ma non viene compilato (anche se non sarebbe anche senza unità di misura presente, ma credo trasmette quello che mi piacerebbe fare):Unità generiche f # su tipi generici

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

l'idea ecco che ho definito alcune unità di misura, ad es "Kg" e "L" e un'unione dicriminated:

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

e vorrei fare qualcosa di simile:

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

Sono consapevole che per questo caso particolare ho potuto semplicemente usare

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

ma ho iniziato a chiedermi il caso generale mentre scrivevo quanto sopra.

+0

Un'altra opzione consiste nell'utilizzare una funzione inline per simulare un'interfaccia. –

risposta

7

Per la vostra domanda specifica come scrivere la vostra funzione isValidUnitValue, la risposta è:

let inline isValidUnitValue myUnit = myUnit > LanguagePrimitives.GenericZero 

Quindi non c'è bisogno di definire un'Unione Discriminazione in.

Riguardo alla domanda iniziale se è possibile definire una funzione che è sia generica su tipo di dati e unità di misura come dropUnit la risposta breve è no. Se tale funzione esiste, avrebbe una firma come 'a<'b> -> 'a e per rappresentarlo il sistema di tipi dovrebbe implementare tipi più alti.

Tuttavia ci sono trucchi che utilizzano il sovraccarico e in linea:

1) Utilizzando i sovraccarichi (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 

Ma questo non è davvero una funzione generica, non è possibile scrivere qualcosa di generico su in alto.

> let inline dropUnitAndAdd3 x = UnitDropper.drop x + 3 ;; 
-> error FS0041: A unique overload for method 'drop' could not be determined ... 


2) Uso di linea, un trucco comune è ridigitare:

let inline retype (x:'a) : 'b = (# "" x : 'b #) 

[<Measure>] type m 
let x = retype 2<m> + 3 
let inline dropUnitAndAdd3 x = retype x + 3 

Il problema è che retype è troppo generico, che vi permetterà di scrivere:

let y = retype 2.0<m> + 3 

Che compila ma non riuscirà in fase di esecuzione.


3) Usando entrambe sovraccarichi e in linea: questo trucco risolverà entrambi i problemi di utilizzo sovraccarico attraverso un tipo intermedio, in questo modo si ottiene sia in fase di compilazione controlli e sarete in grado di definire funzioni generiche :

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 

nell'ultima riga si otterrà un errore di compilazione: FS0001: The type 'int' does not match the type 'float'

un altro vantaggio di questo approccio è che è possibile estendere in un secondo momento con nuovi tipi definendo un me statica mber ($) nella definizione del tipo:

type MyNumericType<[<Measure 'U>]> = 
    ... 
    static member dropUoM (x:MyNumericType<_>) : MyNumericType = ... 
    static member ($) (DropUnit, x:MyNumericType<_>) = MyNumericType.dropUoM(x) 
+0

Ottima risposta, grazie! – Bram