69 votes

Comment créer une fonction haskell polyvariadique ?

J'ai besoin d'une fonction qui prend un nombre arbitraire d'arguments (tous du même type), fait quelque chose avec eux et renvoie ensuite un résultat. Une liste d'arguments est impraticable dans mon cas spécifique.

En parcourant les librairies haskell, j'ai vu que la fonction printf (du module Text.Printf ) utilise une astuce similaire. Malheureusement, je n'ai pas pu comprendre cette magie en regardant la source.

Quelqu'un peut-il m'expliquer comment y parvenir, ou au moins me donner une page web/un document/quelque chose où je pourrais trouver une bonne description de ce processus ?

Motivation :

La raison pour laquelle j'en ai besoin est vraiment très simple. À l'école (cours d'informatique), on nous demande d'écrire un module capable d'"enregistrer" une expression mathématique, de l'exprimer sous forme de chaîne de caractères (via l'écriture d'une instance de Num/Real/etc pour un type de données propre), et d'effectuer diverses opérations sur celle-ci.

Ce type de données contient un constructeur spécial pour une variable, qui peut être remplacée par une valeur ou autre par une fonction spécifiée. L'un des objectifs est d'écrire une fonction, qui prend une telle expression avec un certain nombre de variables (paires de type (Char,Rational) ) et calcule le résultat de l'expression. Nous devrions examiner la meilleure façon d'exprimer le but de la fonction. (Mon idée : La fonction renvoie une autre fonction qui prend exactement autant d'arguments que de variables définies dans la fonction - cela semble impossible).

104voto

KennyTM Points 232647

Les points clés de printf est la possibilité de renvoyer soit une chaîne, soit une fonction. Copié de 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)

et la structure de base que nous pouvons extraire est

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)

Par exemple :

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

alors nous pourrions utiliser

*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

10voto

De nombreuses personnes vous expliquent comment créer des fonctions variadiques, mais je pense que dans ce cas, il est préférable d'utiliser une liste de type [(Char,Rational)].

9voto

lispc Points 1

La réponse de KennyTM est excellente. Vous trouverez ci-dessous un exemple du processus d'exécution de sumOf 1 4 7 10 :: Integer pour donner une meilleure illustration.

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

7voto

delnan Points 52260

Dans l'article wiki sur les fonctions variadiques, cet article a été référencée. Je suppose que c'est ce que fait printf, mais je ne le comprends pas non plus. Quoi qu'il en soit, voici certainement une surenchère, d'autant plus que vos arguments sont tous du même type. Il suffit de les mettre tous dans une seule liste. C'est à ça que servent les listes - un nombre arbitraire de choses du même type. D'accord, ce n'est pas très beau, mais ce ne sera guère plus laid qu'une fonction polyvariable complète.

6voto

Daniel Pratt Points 8151

J'ai jeté un coup d'oeil à un exemple lié à l'article que Delnan a référencé. Après l'avoir regardé pendant un moment, je pense que j'ai finalement compris ce qui se passe :

Cela commence avec cette classe de type :

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

La partie après le tuyau (|) est une dépendance fonctionnelle. Il indique que le type représenté par a peut être déterminé par le type représenté par r . En d'autres termes, on vous empêche de définir deux instances de l'élément BuildList de la même classe de type r (type de retour), mais différents a .

En sautant un peu plus loin, là où le build' est effectivement utilisée :

> build True :: [Bool]

Desde build c'est juste appeler build' avec une liste vide comme premier paramètre, c'est la même chose que :

> build' [] True :: [Bool]

Dans cet exemple, build' renvoie clairement une liste. En raison de la dépendance fonctionnelle, nous ne pouvons nous lier qu'à cette instance de l'objet BuildList classe de type :

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

C'est assez simple. Le deuxième exemple est plus intéressant. En élargissant la définition de build il devient :

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

Quel est le type de build' dans ce cas ? Eh bien, les règles de précédence de Haskell signifient que ce qui précède pourrait également être écrit comme ceci :

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

Maintenant, il devient clair que nous passons deux paramètres à build' puis en appliquant le résultat de cette expression à un paramètre ayant la valeur 'False'. En d'autres termes, l'expression (build' [] True) est censé renvoyer un fonction de type Bool -> [Bool] . Et cela nous lie à la seconde instance de la BuildList typeclass :

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

Dans cette invocation, l = [] y x = True y y = False donc la définition s'étend à build' [True] False :: [Bool] . Cette signature est liée à la première instance de build' et c'est assez évident où ça va à partir de là.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X