Come creare una funzione haskell polyvariadic?

Ho bisogno di una funzione che prende un numero arbitrario di argomenti (tutti dello stesso tipo), fa qualcosa con loro e in seguito restituisce un risultato. Un elenco di argomenti è impraticabile nel mio caso specifico.

Guardando attraverso le librerie haskell, ho visto che la funzione printf (dal modulo Text.Printf ) usa un trucco simile. Sfortunatamente, non riuscivo a capire quella magia guardando la fonte.

Qualcuno può spiegare come ottenere questo, o almeno qualche pagina web / carta / qualunque sia il posto dove trovare una buona descrizione per questo?

Motivazione:

Il motivo per cui ho bisogno di questo è davvero piuttosto semplice. Per la scuola (corso di informatica), siamo tenuti a scrivere un modulo in grado di “registrare” un’espressione matematica, esprimerla come una stringa (tramite la scrittura di un’istanza di Num / Real / etc per un proprio tipo di dati) ed eseguire varie operazioni su di esso.

Questo tipo di dati contiene un costruttore speciale per una variabile, che può essere sostituito da un valore o qualsiasi cosa da una funzione specificata. Uno degli obiettivi è scrivere una funzione, che prende un’espressione di questo tipo con un certo numero di variabili (coppie di tipi (Char,Rational) ) e calcola il risultato dell’espressione. Dovremmo esaminare come esprimere al meglio l’objective della funzione. (La mia idea: la funzione restituisce un’altra funzione che richiede esattamente tanti argomenti quanti i vars che sono definiti nella funzione – sembra essere imansible).

I punti chiave di printf sono la possibilità di restituire una stringa o una funzione. Copiato da http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/src/Text-Printf.html ,

 printf :: (PrintfType r) => String -> r printf fmts = spr fmts [] class PrintfType t where spr :: String -> [UPrintf] -> t instance (IsChar c) => PrintfType [c] where spr fmts args = map fromChar (uprintf fmts (reverse args)) instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where spr fmts args = \a -> spr fmts (toUPrintf a : args) 

e la struttura di base che possiamo estrarre è

 variadicFunction :: VariadicReturnClass r => RequiredArgs -> r variadicFunction reqArgs = variadicImpl reqArgs mempty class VariadicReturnClass r where variadicImpl :: RequiredArgs -> AccumulatingType -> r instance VariadicReturnClass ActualReturnType where variadicImpl reqArgs acc = constructActualResult reqArgs acc instance (ArgClass a, VariadicReturnClass r) => VariadicReturnClass (a -> r) where variadicImpl reqArgs acc = \a -> variadicImpl reqArgs (specialize a `mappend` acc) 

Per esempio:

 class SumRes r where sumOf :: Integer -> r instance SumRes Integer where sumOf = id instance (Integral a, SumRes r) => SumRes (a -> r) where sumOf x = sumOf . (x +) . toInteger 

allora potremmo usare

 *Main> sumOf 1 :: Integer 1 *Main> sumOf 1 4 7 10 :: Integer 22 *Main> sumOf 1 4 7 10 0 0 :: Integer 22 *Main> sumOf 1 4 7 10 2 5 8 22 :: Integer 59 

Un sacco di persone ti dicono come creare funzioni variadiche, ma in questo caso, in realtà, stai meglio usando solo un elenco di tipi [(Char, Rational)].

Nell’articolo wiki sulle funzioni variadic, questo articolo è stato referenziato. Suppongo che questo sia ciò che fa printf, ma non lo capisco neanche io. Ad ogni modo, questo è sicuramente un eccesso, soprattutto perché i tuoi argomenti sono tutti dello stesso tipo. Basta metterli tutti in una lista. Ecco a cosa servono gli elenchi: un numero arbitrario di cose dello stesso tipo. Bene, non è molto bello, ma difficilmente sarà più brutto di una funzione polivalente completa.

Ho dato un’occhiata ad un esempio collegato all’articolo che delnan ha fatto riferimento. Dopo averlo guardato per un po ‘, penso di aver finalmente capito cosa sta succedendo:

Inizia con questa class di tipo:

 class BuildList ar | r-> a where build' :: [a] -> a -> r 

Quel bit dopo la pipe (|) è una dipendenza funzionale. Dice che il tipo rappresentato da a può essere determinato dal tipo rappresentato da r . In altre parole, si è impedito di definire due istanze del BuildList BuildList con lo stesso r (tipo restituito), ma diverso a .

Saltando un po ‘dove viene effettivamente utilizzata la funzione build' :

 > build True :: [Bool] 

Poiché build sta chiamando build' con una lista vuota come primo parametro, è lo stesso di:

 > build' [] True :: [Bool] 

In questo esempio, build' sta chiaramente restituendo una lista. A causa della dipendenza funzionale, possiamo essere vincolanti solo con questa istanza della class di tipi BuildList :

 instance BuildList a [a] where build' lx = reverse$ x:l 

Abbastanza diretto. Il secondo esempio è più interessante. Espandendo la definizione di build , diventa:

 > build' [] True False :: [Bool] 

Qual è il tipo di build' in questo caso? Bene, le regole di precedenza di Haskell significano che quanto sopra potrebbe anche essere scritto in questo modo:

 > (build' [] True) False :: [Bool] 

Ora diventa chiaro che stiamo passando due parametri per build' e quindi applicare il risultato di tale espressione a un parametro con valore’ False ‘. In altre parole, l’espressione (build' [] True) dovrebbe restituire una funzione di tipo Bool -> [Bool] . E questo ci lega alla seconda istanza della BuildList BuildList:

 instance BuildList ar => BuildList a (a->r) where build' lxy = build'(x:l) y 

In questa invocazione, l = [] e x = True e y = False , quindi la definizione si espande per build' [True] False :: [Bool] . Quella firma si lega alla prima istanza di build' , ed è abbastanza ovvio dove va da lì.

La risposta di KennyTM è grandiosa. Di seguito è riportato un esempio del processo exec di sumOf 1 4 7 10 :: Integer per dare una migliore illustrazione.

 sumOf 1 4 7 10 (( \ x -> ( sumOf . (x +) . toInteger ) 1 ) 4 7 10 ((sumOf . (1 + ) . toInteger) 4 ) 7 10 ( sumOf 5 ) 7 10 ( sumOf . (5 + ) . toInteger ) 7 10 sumOf 12 10 sumOf . (12 + ) . toInteger 10 sumof 22 id 22 22