Come scrivere una funzione per numeri generici?

Sono abbastanza nuovo per F # e trovare l’inferenza di tipo è davvero una cosa interessante. Ma al momento sembra che potrebbe anche portare alla duplicazione del codice, che non è una cosa interessante . Voglio sumre le cifre di un numero come questo:

let rec crossfoot n = if n = 0 then 0 else n % 10 + crossfoot (n / 10) crossfoot 123 

Questo stampa correttamente 6 . Ma ora il mio numero di input non si adatta a 32 bit int, quindi devo trasformarlo in.

 let rec crossfoot n = if n = 0L then 0L else n % 10L + crossfoot (n / 10L) crossfoot 123L 

Quindi, un BigInteger viene a modo mio e indovina cosa …

Ovviamente, potrei avere solo la versione bigint e lanciare i parametri di input e rilasciare i parametri in base alle esigenze. Ma prima suppongo che l’utilizzo di BigInteger su int abbia qualche BigInteger delle prestazioni. In secondo luogo, let cf = int (crossfoot (bigint 123)) non si legge bene.

Non c’è un modo generico per scrivere questo?

Basandosi sulle risposte di Brian e Stephen, ecco un codice completo:

 module NumericLiteralG = let inline FromZero() = LanguagePrimitives.GenericZero let inline FromOne() = LanguagePrimitives.GenericOne let inline FromInt32 (n:int) = let one : ^a = FromOne() let zero : ^a = FromZero() let n_incr = if n > 0 then 1 else -1 let g_incr = if n > 0 then one else (zero - one) let rec loop ig = if i = n then g else loop (i + n_incr) (g + g_incr) loop 0 zero let inline crossfoot (n:^a) : ^a = let (zero:^a) = 0G let (ten:^a) = 10G let rec compute (n:^a) = if n = zero then zero else ((n % ten):^a) + compute (n / ten) compute n crossfoot 123 crossfoot 123I crossfoot 123L 

AGGIORNAMENTO: risposta semplice

Ecco un’implementazione standalone, senza il modulo NumericLiteralG , e un tipo inferito leggermente meno restrittivo:

 let inline crossfoot (n:^a) : ^a = let zero:^a = LanguagePrimitives.GenericZero let ten:^a = (Seq.init 10 (fun _ -> LanguagePrimitives.GenericOne)) |> Seq.sum let rec compute (n:^a) = if n = zero then zero else ((n % ten):^a) + compute (n / ten) compute n 

Spiegazione

Esistono in effetti due tipi di generici in F #: 1) polimorfismo di tipo run, tramite interfacce / ereditarietà .NET e 2) generici di tempo di compilazione. I generici in fase di compilazione sono necessari per ospitare cose come operazioni numeriche generiche e qualcosa come digitazione anatra ( vincoli espliciti dei membri ). Queste funzionalità sono integrate in F # ma non supportate in .NET, quindi devono essere gestite da F # in fase di compilazione.

Il segno di omissione ( ^ ) viene utilizzato per differenziare i parametri di tipo statically resolved (in fase di compilazione) da quelli ordinari (che utilizzano un apostrofo). In breve, 'a viene gestito in fase di esecuzione, ^a in fase di compilazione, motivo per cui la funzione deve essere contrassegnata in inline .

Non avevo mai provato a scrivere qualcosa di simile prima. Si è rivelato più goffamente di quanto mi aspettassi. Il più big ostacolo che vedo scrivere codice numerico generico in F # è: creare un’istanza di un numero generico diverso da zero o uno. Vedere l’implementazione di FromInt32 in questa risposta per vedere cosa intendo. GenericZero e GenericOne sono integrati e vengono implementati utilizzando tecniche che non sono disponibili nel codice utente. In questa funzione, poiché avevamo solo bisogno di un numero piccolo (10), ho creato una sequenza di 10 GenericOne e li GenericOne sumti.

Non riesco a spiegare perché siano necessarie tutte le annotazioni del tipo, tranne per dire che appare ogni volta che il compilatore incontra un’operazione su un tipo generico e sembra pensare che si tratti di un nuovo tipo. Quindi finisce per dedurre qualche tipo bizzarro con resitrictions duplicati (ad esempio potrebbe richiedere (+) più volte). Aggiungendo le annotazioni sui tipi, sappiamo che abbiamo a che fare con lo stesso tipo in tutto. Il codice funziona bene senza di loro, ma aggiungerli semplifica la firma dedotta.

Oltre alla tecnica di kvb che utilizza Numeric Literals (il collegamento di Brian), ho avuto molto successo utilizzando una tecnica diversa che può produrre più firme di tipo strutturale inferito e può anche essere usata per creare funzioni specifiche precompute per migliorare le prestazioni e come controllo sui tipi numerici supportati (poiché spesso si desidera supportare tutti i tipi interi, ma non i tipi razionali, ad esempio): F # Vincoli di tipo dei membri statici .

In seguito alla discussione che ho avuto con Daniel e I sulle firme di tipo inferito fornite dalle diverse tecniche, ecco una panoramica:

Tecnica NumericLiteralG

 module NumericLiteralG = let inline FromZero() = LanguagePrimitives.GenericZero let inline FromOne() = LanguagePrimitives.GenericOne let inline FromInt32 (n:int) = let one = FromOne() let zero = FromZero() let n_incr = if n > 0 then 1 else -1 let g_incr = if n > 0 then one else (zero - one) let rec loop ig = if i = n then g else loop (i + n_incr) (g + g_incr) loop 0 zero 

Crossfoot senza aggiungere annotazioni di tipo:

 let inline crossfoot1 n = let rec compute n = if n = 0G then 0G else n % 10G + compute (n / 10G) compute n val inline crossfoot1 : ^a -> ^e when ( ^a or ^b) : (static member ( % ) : ^a * ^b -> ^d) and ^a : (static member get_Zero : -> ^a) and ( ^a or ^f) : (static member ( / ) : ^a * ^f -> ^a) and ^a : equality and ^b : (static member get_Zero : -> ^b) and ( ^b or ^c) : (static member ( - ) : ^b * ^c -> ^c) and ( ^b or ^c) : (static member ( + ) : ^b * ^c -> ^b) and ^c : (static member get_One : -> ^c) and ( ^d or ^e) : (static member ( + ) : ^d * ^e -> ^e) and ^e : (static member get_Zero : -> ^e) and ^f : (static member get_Zero : -> ^f) and ( ^f or ^g) : (static member ( - ) : ^f * ^g -> ^g) and ( ^f or ^g) : (static member ( + ) : ^f * ^g -> ^f) and ^g : (static member get_One : -> ^g) 

Crossfoot aggiungendo alcune annotazioni di tipo:

 let inline crossfoot2 (n:^a) : ^a = let (zero:^a) = 0G let (ten:^a) = 10G let rec compute (n:^a) = if n = zero then zero else ((n % ten):^a) + compute (n / ten) compute n val inline crossfoot2 : ^a -> ^a when ^a : (static member get_Zero : -> ^a) and ( ^a or ^a0) : (static member ( - ) : ^a * ^a0 -> ^a0) and ( ^a or ^a0) : (static member ( + ) : ^a * ^a0 -> ^a) and ^a : equality and ^a : (static member ( + ) : ^a * ^a -> ^a) and ^a : (static member ( % ) : ^a * ^a -> ^a) and ^a : (static member ( / ) : ^a * ^a -> ^a) and ^a0 : (static member get_One : -> ^a0) 

Tipo di registrazione tecnica

 module LP = 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 } open LP 

Crossfoot, nessuna annotazione richiesta per una firma con deduzione piacevole:

 let inline crossfoot3 n = let g = G_of n let ten = g.any 10 let rec compute n = if n = g.zero then g.zero else n % ten + compute (n / ten) compute n val inline crossfoot3 : ^a -> ^a when ^a : (static member ( % ) : ^a * ^a -> ^b) and ( ^b or ^a) : (static member ( + ) : ^b * ^a -> ^a) and ^a : (static member get_Zero : -> ^a) and ^a : (static member get_One : -> ^a) and ^a : (static member ( + ) : ^a * ^a -> ^a) and ^a : (static member ( - ) : ^a * ^a -> ^a) and ^a : equality and ^a : (static member ( / ) : ^a * ^a -> ^a) 

Crossfoot, nessuna annotazione, accetta istanze pre-calcolate di G:

 let inline crossfootG g ten n = let rec compute n = if n = g.zero then g.zero else n % ten + compute (n / ten) compute n val inline crossfootG : G< ^a> -> ^b -> ^a -> ^a when ( ^a or ^b) : (static member ( % ) : ^a * ^b -> ^c) and ( ^c or ^a) : (static member ( + ) : ^c * ^a -> ^a) and ( ^a or ^b) : (static member ( / ) : ^a * ^b -> ^a) and ^a : equality 

Uso in pratica quanto sopra, da allora posso creare versioni specifiche del tipo precalcolate che non soffrono del costo delle prestazioni di Generic LanguagePrimitives:

 let gn = G_of 1 //int32 let gL = G_of 1L //int64 let gI = G_of 1I //bigint let gD = G_of 1.0 //double let gS = G_of 1.0f //single let gM = G_of 1.0m //decimal let crossfootn = crossfootG gn (gn.any 10) let crossfootL = crossfootG gL (gL.any 10) let crossfootI = crossfootG gI (gI.any 10) let crossfootD = crossfootG gD (gD.any 10) let crossfootS = crossfootG gS (gS.any 10) let crossfootM = crossfootG gM (gM.any 10) 

Dato che la questione di come rendere le impronte di tipo meno pelose quando si usano i letterali numerici generalizzati è venuta fuori, ho pensato di inserire i miei due centesimi. Il problema principale è che gli operatori di F # possono essere asimmetrici in modo da poter fare cose come System.DateTime.Now + System.TimeSpan.FromHours(1.0) , il che significa che l’inferenza di tipo di F # aggiunge variabili di tipo intermedio ogni volta che vengono eseguite operazioni aritmetiche.

Nel caso di algoritmi numerici, questa potenziale asimmetria non è tipicamente utile e l’esplosione risultante nelle firme del tipo è abbastanza brutta (anche se generalmente non influisce sulla capacità di F # di applicare correttamente le funzioni quando vengono forniti argomenti concreti). Una ansible soluzione a questo problema consiste nel limitare i tipi di operatori aritmetici all’interno dell’ambito a cui tieni. Ad esempio, se si definisce questo modulo:

 module SymmetricOps = let inline (+) (x:'a) (y:'a) : 'a = x + y let inline (-) (x:'a) (y:'a) : 'a = x - y let inline (*) (x:'a) (y:'a) : 'a = x * y let inline (/) (x:'a) (y:'a) : 'a = x / y let inline (%) (x:'a) (y:'a) : 'a = x % y ... 

quindi puoi semplicemente aprire il modulo SymmetricOps ogni volta che vuoi che gli operatori applichino solo a due argomenti dello stesso tipo. Quindi ora possiamo definire:

 module NumericLiteralG = open SymmetricOps let inline FromZero() = LanguagePrimitives.GenericZero let inline FromOne() = LanguagePrimitives.GenericOne let inline FromInt32 (n:int) = let one = FromOne() let zero = FromZero() let n_incr = if n > 0 then 1 else -1 let g_incr = if n > 0 then one else (zero - one) let rec loop ig = if i = n then g else loop (i + n_incr) (g + g_incr) loop 0 zero 

e

 open SymmetricOps let inline crossfoot x = let rec compute n = if n = 0G then 0G else n % 10G + compute (n / 10G) compute x 

e il tipo inferito è relativamente pulito

 val inline crossfoot : ^a -> ^a when ^a : (static member ( - ) : ^a * ^a -> ^a) and ^a : (static member get_One : -> ^a) and ^a : (static member ( % ) : ^a * ^a -> ^a) and ^a : (static member get_Zero : -> ^a) and ^a : (static member ( + ) : ^a * ^a -> ^a) and ^a : (static member ( / ) : ^a * ^a -> ^a) and ^a : equality 

mentre otteniamo ancora il vantaggio di una definizione piacevole e leggibile per il crossfoot .

Vedere

Caratteristiche nascoste di F #

Mi sono imbattuto in questo argomento quando stavo cercando una soluzione e sto postando la mia risposta, perché ho trovato un modo per esprimere numeri generici senza l’implementazione non ottimale di build il numero a mano.

 open System.Numerics // optional open MathNet.Numerics module NumericLiteralG = type GenericNumber = GenericNumber with static member instance (GenericNumber, x:int32, _:int8) = fun () -> int8 x static member instance (GenericNumber, x:int32, _:uint8) = fun () -> uint8 x static member instance (GenericNumber, x:int32, _:int16) = fun () -> int16 x static member instance (GenericNumber, x:int32, _:uint16) = fun () -> uint16 x static member instance (GenericNumber, x:int32, _:int32) = fun () -> x static member instance (GenericNumber, x:int32, _:uint32) = fun () -> uint32 x static member instance (GenericNumber, x:int32, _:int64) = fun () -> int64 x static member instance (GenericNumber, x:int32, _:uint64) = fun () -> uint64 x static member instance (GenericNumber, x:int32, _:float32) = fun () -> float32 x static member instance (GenericNumber, x:int32, _:float) = fun () -> float x static member instance (GenericNumber, x:int32, _:bigint) = fun () -> bigint x static member instance (GenericNumber, x:int32, _:decimal) = fun () -> decimal x static member instance (GenericNumber, x:int32, _:Complex) = fun () -> Complex.op_Implicit x static member instance (GenericNumber, x:int64, _:int64) = fun () -> int64 x static member instance (GenericNumber, x:int64, _:uint64) = fun () -> uint64 x static member instance (GenericNumber, x:int64, _:float32) = fun () -> float32 x static member instance (GenericNumber, x:int64, _:float) = fun () -> float x static member instance (GenericNumber, x:int64, _:bigint) = fun () -> bigint x static member instance (GenericNumber, x:int64, _:decimal) = fun () -> decimal x static member instance (GenericNumber, x:int64, _:Complex) = fun () -> Complex.op_Implicit x static member instance (GenericNumber, x:string, _:float32) = fun () -> float32 x static member instance (GenericNumber, x:string, _:float) = fun () -> float x static member instance (GenericNumber, x:string, _:bigint) = fun () -> bigint.Parse x static member instance (GenericNumber, x:string, _:decimal) = fun () -> decimal x static member instance (GenericNumber, x:string, _:Complex) = fun () -> Complex(float x, 0.0) // MathNet.Numerics static member instance (GenericNumber, x:int32, _:Complex32) = fun () -> Complex32.op_Implicit x static member instance (GenericNumber, x:int32, _:bignum) = fun () -> bignum.FromInt x static member instance (GenericNumber, x:int64, _:Complex32) = fun () -> Complex32.op_Implicit x static member instance (GenericNumber, x:int64, _:bignum) = fun () -> bignum.FromBigInt (bigint x) static member instance (GenericNumber, x:string, _:Complex32) = fun () -> Complex32(float32 x, 0.0f) static member instance (GenericNumber, x:string, _:bignum) = fun () -> bignum.FromBigInt (bigint.Parse x) let inline genericNumber num = Inline.instance (GenericNumber, num) () let inline FromZero () = LanguagePrimitives.GenericZero let inline FromOne () = LanguagePrimitives.GenericOne let inline FromInt32 n = genericNumber n let inline FromInt64 n = genericNumber n let inline FromString n = genericNumber n 

questa implementazione arriva senza complicate iterazioni durante il cast. Usa FsControl per il modulo Instance.

http://www.fssnip.net/mv

Crossfoot è esattamente quello che vuoi fare o è solo la sum delle cifre di un numero lungo?

perché se vuoi solo sumre le cifre, allora:

 let crossfoot (x:'a) = x.ToString().ToCharArray() |> (Array.fold(fun acc x' -> if x' <> '.' then acc + (int x') else acc) 0) 

… E hai finito.

Ad ogni modo, puoi convertire cose in una stringa, rilasciare il punto decimale, ricordare dove si trova il punto decimale, interpretarlo come un int, eseguire crossfoot?

Ecco la mia soluzione. Non sono sicuro di come vuoi che “crossfoot” funzioni quando hai aggiunto un punto decimale.

Ad esempio, vuoi: crossfoot(123.1) = 7 o crossfoot(123.1) = 6.1 ? (Suppongo che tu voglia il secondo)

Ad ogni modo, il codice ti consente di lavorare con i numeri come generici.

 let crossfoot (n:'a) = // Completely generic input let rec crossfoot' (a:int) = // Standard integer crossfoot if a = 0 then 0 else a%10 + crossfoot' (a / 10) let nstr = n.ToString() let nn = nstr.Split([|'.'|]) // Assuming your main constraint is float/int let n',n_ = if nn.Length > 1 then nn.[0],nn.[1] else nn.[0],"0" let n'',n_' = crossfoot'(int n'),crossfoot'(int n_) match n_' with | 0 -> string n'' | _ -> (string n'')+"."+(string n_') 

Se hai bisogno di inserire interi di grandi dimensioni o int64, il modo in cui funziona crossfoot, puoi semplicemente dividere il grande numero in blocchi di bit (stringhe) e dar loro da mangiare in questa funzione, e aggiungerli insieme.