Oui c'est ça para
. Comparez avec le catamorphisme, ou foldr
:
para :: (a -> [a] -> b -> b) -> b -> [a] -> b
foldr :: (a -> b -> b) -> b -> [a] -> b
para c n (x : xs) = c x xs (para c n xs)
foldr c n (x : xs) = c x (foldr c n xs)
para c n [] = n
foldr c n [] = n
Certaines personnes appellent les paramorphismes «récursion primitive» par opposition aux catamorphismes ( foldr
) étant «itération».
Où foldr
les deux paramètres reçoivent une valeur calculée de manière récursive pour chaque sous-objet récursif des données d'entrée (ici, c'est la queue de la liste), para
les paramètres de s obtiennent à la fois le sous-objet d'origine et la valeur calculée de manière récursive à partir de celui-ci.
Un exemple de fonction qui est joliment exprimé avec para
est la collection des suffisants appropriés d'une liste.
suff :: [x] -> [[x]]
suff = para (\ x xs suffxs -> xs : suffxs) []
pour que
suff "suffix" = ["uffix", "ffix", "fix", "ix", "x", ""]
Peut-être encore plus simple est-il
safeTail :: [x] -> Maybe [x]
safeTail = para (\ _ xs _ -> Just xs) Nothing
dans lequel la branche "cons" ignore son argument récursivement calculé et rend juste la queue. Evalué paresseusement, le calcul récursif ne se produit jamais et la queue est extraite en temps constant.
Vous pouvez définir en foldr
utilisant para
assez facilement; il est un peu plus délicat à définir à para
partir foldr
, mais il est certainement possible, et tout le monde devrait savoir comment faire!
foldr c n = para (\ x xs t -> c x t) n
para c n = snd . foldr (\ x (xs, t) -> (x : xs, c x xs t)) ([], n)
L'astuce pour définir para
avec foldr
est de reconstruire une copie des données originales, de sorte que nous ayons accès à une copie de la queue à chaque étape, même si nous n'avions pas accès à l'original. À la fin, snd
supprime la copie de l'entrée et donne juste la valeur de sortie. Ce n'est pas très efficace, mais si vous êtes intéressé par l'expressivité pure, para
ne vous en donne pas plus foldr
. Si vous utilisez cette foldr
version codée de para
, safeTail
cela prendra un temps linéaire après tout, la copie de la queue élément par élément.
Alors, c'est tout: para
c'est une version plus pratique foldr
qui vous donne un accès immédiat à la queue de la liste ainsi qu'à la valeur calculée à partir de celle-ci.
Dans le cas général, travailler avec un type de données généré comme point fixe récursif d'un foncteur
data Fix f = In (f (Fix f))
vous avez
cata :: Functor f => (f t -> t) -> Fix f -> t
para :: Functor f => (f (Fix f, t) -> t) -> Fix f -> t
cata phi (In ff) = phi (fmap (cata phi) ff)
para psi (In ff) = psi (fmap keepCopy ff) where
keepCopy x = (x, para psi x)
et encore une fois, les deux sont mutuellement définissables, avec para
définis à partir cata
de la même astuce "faire une copie"
para psi = snd . cata (\ fxt -> (In (fmap fst fxt), psi fxt))
Encore une fois, ce para
n'est pas plus expressif que cata
, mais plus pratique si vous avez besoin d'un accès facile aux sous-structures de l'entrée.
Edit: Je me suis souvenu d'un autre bel exemple.
Considérons les arbres de recherche binaires donnés par Fix TreeF
où
data TreeF sub = Leaf | Node sub Integer sub
et essayez de définir l'insertion pour les arbres de recherche binaires, d'abord comme a cata
, puis comme a para
. Vous trouverez la para
version beaucoup plus facile, car à chaque nœud, vous devrez insérer dans un sous-arbre mais conserver l'autre tel qu'il était.
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs)
, il me semble.