Vincoli di tipo membro statico F #

Sto cercando di definire una funzione, fattorizzazione, che utilizza i vincoli di tipo strutturale (richiede membri statici Zero, Uno, + e /) simile a Seq.sum in modo che possa essere utilizzato con int, long, bigint, ecc. I non riesco a trovare la syntax giusta, e non riesco a trovare molte risorse sull’argomento. Questo è quello che ho, per favore aiuto.

let inline factorize (n:^NUM) = ^NUM : (static member get_Zero: unit->(^NUM)) ^NUM : (static member get_One: unit->(^NUM)) let rec factorize (n:^NUM) (j:^NUM) (flist: ^NUM list) = if n = ^NUM.One then flist elif n % j = ^NUM.Zero then factorize (n/j) (^NUM.One + ^NUM.One) (j::flist) else factorize n (j + ^NUM.One) (flist) factorize n (^NUM.One + ^NUM.One) [] 

Ecco come lo scriverei:

 module NumericLiteralG = begin let inline FromZero() = LanguagePrimitives.GenericZero let inline FromOne() = LanguagePrimitives.GenericOne end let inline factorize n = let rec factorize nj flist = if n = 1G then flist elif n % j = 0G then factorize (n/j) j (j::flist) else factorize n (j + 1G) (flist) factorize n (1G + 1G) [] 

Il tipo inferito per il fattorizzazione qui è troppo generico, ma la funzione funzionerà come ci si aspetterebbe. È ansible forzare una firma e un insieme di limiti più sane se si desidera aggiungendo tipi espliciti ad alcune espressioni generiche:

 let inline factorize (n:^a) : ^a list = let (one : ^a) = 1G let (zero : ^a) = 0G let rec factorize n (j:^a) flist = if n = one then flist elif n % j = zero then factorize (n/j) j (j::flist) else factorize n (j + one) (flist) factorize n (one + one) [] 

Ispirato alla risposta di kvb usando NumericLiterals, sono stato spinto a sviluppare un approccio che ci permettesse di forzare le firme di tipo “sane” senza dover aggiungere estese annotazioni di tipo.

Per prima cosa definiamo alcune funzioni di supporto e il tipo di wrapper per le primitive della lingua:

 let inline zero_of (target:'a) : 'a = LanguagePrimitives.GenericZero<'a> let inline one_of (target:'a) : 'a = LanguagePrimitives.GenericOne<'a> let inline two_of (target:'a) : 'a = one_of(target) + one_of(target) let inline three_of (target:'a) : 'a = two_of(target) + one_of(target) let inline negone_of (target:'a) : 'a = zero_of(target) - one_of(target) let inline any_of (target:'a) (x:int) : 'a = let one:'a = one_of target let zero:'a = zero_of target let xu = if x > 0 then 1 else -1 let gu:'a = if x > 0 then one else zero-one let rec get ig = if i = x then g else get (i+xu) (g+gu) get 0 zero type G<'a> = { negone:'a zero:'a one:'a two:'a three:'a any: int -> 'a } let inline G_of (target:'a) : (G<'a>) = { zero = zero_of target one = one_of target two = two_of target three = three_of target negone = negone_of target any = any_of target } 

Poi abbiamo:

 let inline factorizeG n = let g = G_of n let rec factorize nj flist = if n = g.one then flist elif n % j = g.zero then factorize (n/j) j (j::flist) else factorize n (j + g.one) (flist) factorize n g.two [] 

[ Modifica : a causa di un bug apparente con F # 2.0 / .NET 2.0, factorizen, factorizeL e factorizeI di seguito vengono eseguiti molto più lentamente di factorizeG quando compilati in modalità Release, ma altrimenti vengono eseguiti leggermente più velocemente come previsto – vedi domanda di prestazioni F #: cosa è il compilatore sta facendo? ]

Oppure possiamo fare qualche passo in più (ispirato a Expert F #, p.110):

 let inline factorize (g:G<'a>) n = //' let rec factorize nj flist = if n = g.one then flist elif n % j = g.zero then factorize (n/j) j (j::flist) else factorize n (j + g.one) (flist) factorize n g.two [] //identical to our earlier factorizeG let inline factorizeG n = factorize (G_of n) n let gn = G_of 1 //int32 let gL = G_of 1L //int64 let gI = G_of 1I //bigint //allow us to limit to only integral numeric types //and to reap performance gain by using pre-computed instances of G let factorizen = factorize gn let factorizeL = factorize gL let factorizeI = factorize gI 

Inoltre, ecco una versione estesa di NumericLiteralG di kvb che ci permette di usare “2G”, “-8G”, ecc. Sebbene non sia ansible capire come implementare una strategia di memoizzazione (sebbene ciò dovrebbe essere fattibile per G.any) .

 module NumericLiteralG = let inline FromZero() = LanguagePrimitives.GenericZero let inline FromOne() = LanguagePrimitives.GenericOne let inline FromInt32(n:int):'a = let one:'a = FromOne() let zero:'a = FromZero() let nu = if n > 0 then 1 else -1 let gu:'a = if n > 0 then one else zero-one let rec get ig = if i = n then g else get (i+nu) (g+gu) get 0 zero 

Innanzitutto, ecco un esempio banale che mostra come dovrebbe apparire la syntax:

 let inline zero< ^NUM when ^NUM : (static member get_Zero: unit-> ^NUM)> (n:^NUM) = (^NUM : (static member get_Zero : unit -> ^NUM) ()) 

In alcuni casi, non è necessario scrivere i vincoli in modo esplicito (il compilatore F # ti avviserà effettivamente di ciò se scrivi quanto sopra), perché alcuni membri statici sono ben noti al compilatore e ci sono funzioni standard per usarli . Quindi, puoi usare la funzione e il compilatore dedurrà il vincolo:

 let inline zero (n:^T) = LanguagePrimitives.GenericZero< ^T > 

Sfortunatamente, questo non ti aiuta, perché le funzioni ricorsive non possono essere dichiarate come inline (per ovvi motivi – il compilatore non può inline la funzione in fase di compilazione, perché non sa quante volte), quindi i vincoli statici probabilmente non sono abbastanza potente per il tuo problema.

[ MODIFICA : questo è effettivamente ansible per alcune funzioni (vedi la risposta di kvb)]

Penso che avrete bisogno invece di NumericAssociations , che sono stati discussi in questa domanda (questi sono elaborati in fase di esecuzione, quindi sono più lenti – ma sono usati per implementare per esempio il tipo di matrice F # – la matrice può memorizzare le informazioni acquisite dynamicmente, quindi è ragionevolmente efficiente).