38 votes

Histomorphisms, Zygomorphisms et Futumorphisms spécialisés pour les listes

J'ai fini par essayer de le comprendre. Voir la vidéo et les slides d'une conférence que j'ai donné:

Question de départ:

Dans mon effort pour comprendre générique schémas de récursion (c'est à dire, que l'utilisation Fix) je trouve qu'il est utile d'écrire la liste des versions des différents régimes. Il rend beaucoup plus facile de comprendre le réel régimes (sans la surcharge supplémentaire de l' Fix trucs).

Cependant, je n'ai pas encore compris comment définir la liste des versions de l' zygo et futu.

Voici mon spécialisé définitions de la mesure:

cataL :: (a ->        b -> b) -> b -> [a] -> b
cataL f b (a : as) = f a    (cataL f b as)
cataL _ b []       = b

paraL :: (a -> [a] -> b -> b) -> b -> [a] -> b
paraL f b (a : as) = f a as (paraL f b as)
paraL _ b []       = b

-- TODO: histo

-- DONE: zygo (see below)

anaL  :: (b ->       (a, b))               -> b -> [a]
anaL  f b = let (a, b') = f b in a : anaL f b'

anaL' :: (b -> Maybe (a, b))               -> b -> [a]
anaL' f b = case f b of
    Just (a, b') -> a : anaL' f b'
    Nothing      -> []

apoL :: ([b] -> Maybe (a, Either [b] [a])) -> [b] -> [a]
apoL f b = case f b of
    Nothing -> []
    Just (x, Left c)  -> x : apoL f c
    Just (x, Right e) -> x : e

-- DONE: futu (see below)

hyloL  :: (a -> c -> c) -> c -> (b -> Maybe (a, b)) -> b -> c
hyloL f z g = cataL f z . anaL' g

hyloL' :: (a -> c -> c) -> c -> (c -> Maybe (a, c))      -> c
hyloL' f z g = case g z of
    Nothing     -> z
    Just (x,z') -> f x (hyloL' f z' g)

Comment définissez-vous histo, zygo et futu pour les listes?

40voto

Benjamin Hodgson Points 3510

Zygomorphism est de la haute-falutin' mathsy nom que nous donnons à plis construite à partir de deux semi-mutuellement des fonctions récursives. Je vais vous donner un exemple.

Imaginez une fonction pm :: [Int] -> Int (pour les plus-moins) qui intersperses + et - en alternance par le biais d'une liste de nombres, tels que pm [v,w,x,y,z] = v - (w + (x - (y + z))). Vous pouvez l'écrire à l'aide de primitives de récursivité:

lengthEven :: [a] -> Bool
lengthEven = even . length

pm0 [] = 0
pm0 (x:xs) = if lengthEven xs
             then x - pm0 xs
             else x + pm0 xs

Clairement pm0 n'est pas de la composition - , vous devez vérifier la longueur de l'ensemble de la liste à chaque position pour déterminer si vous êtes l'ajout ou la soustraction. Paramorphism modèles de la récursion primitive de ce genre, lorsque la fonction de pliage doit parcourir l'ensemble de la sous-arborescence à chaque itération de la pliure. Si au moins nous pouvons réécrire le code pour être conforme à un modèle établi.

paraL :: (a -> [a] -> b -> b) -> b -> [a] -> b
paraL f z [] = z
paraL f z (x:xs) = f x xs (paraL f z xs)

pm1 = paraL (\x xs acc -> if lengthEven xs then x - acc else x + acc) 0

Mais c'est inefficace. lengthEven traverse toute la liste à chaque itération de la paramorphism résultant en un temps O(n2) de l'algorithme.


Nous pouvons faire des progrès en notant que les deux lengthEven et para peut être exprimée comme une catamorphism avec foldr...

cataL = foldr

lengthEven' = cataL (\_ p -> not p) True
paraL' f z = snd . cataL (\x (xs, acc) -> (x:xs, f x xs acc)) ([], z)

... ce qui suggère que nous pourrions être en mesure de fusionner les deux opérations en un seul passage sur la liste.

pm2 = snd . cataL (\x (isEven, total) -> (not isEven, if isEven
                                                      then x - total
                                                      else x + total)) (True, 0)

Nous avons eu un pli qui dépendait du résultat de l'autre fois, et nous avons été en mesure de les faire fusionner en un seul parcours de la liste. Zygomorphism capture exactement ce modèle.

zygoL :: (a -> b -> b) ->  -- a folding function
         (a -> b -> c -> c) ->  -- a folding function which depends on the result of the other fold
         b -> c ->  -- zeroes for the two folds
         [a] -> c
zygoL f g z e = snd . cataL (\x (p, q) -> (f x p, g x p q)) (z, e)

À chaque itération de la bergerie, f voit sa réponse à partir de la dernière itération comme dans un catamorphism, mais g arrive à voir à la fois les fonctions de réponses. g entangles lui-même avec f.

Nous allons écrire pm comme un zygomorphism par l'aide de la première fonction de pliage à compter de savoir si la liste est pair ou impair dans la longueur et la seconde pour calculer le total.

pm3 = zygoL (\_ p -> not p) (\x isEven total -> if isEven
                                                then x - total
                                                else x + total) True 0

C'est classique de la programmation fonctionnelle style. Nous avons un ordre supérieur de la fonction de faire de levage lourds de la consommation de la liste; tout ce que nous avions à faire était prise dans la logique de l'ensemble des résultats. La construction de toute évidence se termine (vous avez seulement besoin de prouver la terminaison pour foldr), et il est plus efficace que l'original, écrit à la main la version de boot.

De côté: @AlexR points dans les commentaires que zygomorphism a une grande soeur nommée mutumorphism, qui capte la récursivité mutuelle dans toutes les sa gloire. mutu généralise zygo dans les deux le pliage les fonctions sont autorisés à inspecter les autres à la suite de la précédente itération.

mutuL :: (a -> b -> c -> b) ->
         (a -> b -> c -> c) ->
         b -> c ->
         [a] -> c
mutuL f g z e = snd . cataL (\x (p, q) -> (f x p q, g x p q)) (z, e)

Vous récupérez zygo de mutu simplement en ignorant l'argument supplémentaire. zygoL f = mutuL (\x p q -> f x p)


Bien sûr, tous ces modèles de pliage de généraliser à partir de listes à la fixpoint de l'arbitraire d'un foncteur:

newtype Fix f = Fix { unFix :: f (Fix f) }

cata :: Functor f => (f a -> a) -> Fix f -> a
cata f = f . fmap (cata f) . unFix

para :: Functor f => (f (Fix f, a) -> a) -> Fix f -> a
para f = snd . cata (\x -> (Fix $ fmap fst x, f x))

zygo :: Functor f => (f b -> b) -> (f (b, a) -> a) -> Fix f -> a
zygo f g = snd . cata (\x -> (f $ fmap fst x, g x))

mutu :: Functor f => (f (b, a) -> b) -> (f (b, a) -> a) -> Fix f -> a
mutu f g = snd . cata (\x -> (f x, g x))

Comparer la définition de l' zygo avec zygoL. Notez aussi que l' zygo Fix = para, et que les trois derniers plis peuvent être mis en œuvre en termes de cata. Dans foldology tout est lié à tout le reste.

Vous pouvez récupérer la version de liste à partir de la version généralisée.

data ListF a r = Nil_ | Cons_ a r deriving Functor
type List a = Fix (ListF a)

zygoL' :: (a -> b -> b) -> (a -> b -> c -> c) -> b -> c -> List a -> c
zygoL' f g z e = zygo k l
    where k Nil_ = z
          k (Cons_ x y) = f x y
          l Nil_ = e
          l (Cons_ x (y, z)) = g x y z

pm4 = zygoL' (\_ p -> not p) (\x isEven total -> if isEven
                                                 then x - total
                                                 else x + total) True 0

13voto

Alex R Points 694

Puisque personne n'a répondu pour futu encore, je vais essayer de buter mon chemin à travers. Je vais utiliser ListF a b = Base [a] = ConsF a b | NilF

En prenant le type en recursion-schemes: futu :: Unfoldable t => (a -> Base t (Free (Base t) a)) -> a -> t.

Je vais ignorer l' Unfoldable de la contrainte et de substituer [b] pour t.

(a -> Base [b] (Free (Base [b]) a)) -> a -> [b]
(a -> ListF b (Free (ListF b) a)) -> a -> [b]

Free (ListF b) a) est une liste, peut-être avec un a-tapé trou à la fin. Cela signifie qu'il est isomorphe a' ([b], Maybe a). Alors maintenant, nous avons:

(a -> ListF b ([b], Maybe a)) -> a -> [b]

L'élimination du dernier ListF, en remarquant qu' ListF a b est isomorphe a' Maybe (a, b):

(a -> Maybe (b, ([b], Maybe a))) -> a -> [b]

Maintenant, je suis assez sûr que le jeu de type tetris conduit à la seule mise en pratique sensible:

futuL f x = case f x of
  Nothing -> []
  Just (y, (ys, mz)) -> y : (ys ++ fz)
    where fz = case mz of
      Nothing -> []
      Just z -> futuL f z

Résumant la fonction obtenue, futuL prend une valeur de départ et une fonction qui peut produire au moins un résultat, et, éventuellement, un nouveau lot de semences de valeur que si elle produit un résultat.

Au début, je pensais que c'était équivalent à

notFutuL :: (a -> ([b], Maybe a)) -> a -> [b]
notFutuL f x = case f x of
  (ys, mx) -> ys ++ case mx of
    Nothing -> []
    Just x' -> notFutuL f x'

Et dans la pratique, c'est peut-être, plus ou moins, mais la seule différence importante est que les véritables futu garantit la productivité (par exemple, si f retourne toujours, vous ne serez jamais bloqué en attente pour toujours le prochain élément de la liste).

13voto

Benjamin Hodgson Points 3510

Histomorphism modèles de programmation dynamique, la technique consistant à exploiter les résultats de la précédente subcomputations. (Il est parfois appelé bien sûr de la valeur de l'induction.) Dans un histomorphism, la fonction de pliage a accès à un tableau des résultats des précédentes itérations de la pliure. A comparer avec le catamorphism, où la fonction de pliage ne pouvez voir le résultat de la dernière itération. Le histomorphism a l'avantage de recul - vous pouvez voir l'ensemble de l'histoire.

Voici l'idée. Comme nous consommons de la liste d'entrée, le pliage algèbre de sortie une séquence de bs. histo permettra de noter chaque b comme il ressort, en l'attachant à la table des résultats. Le nombre d'éléments dans l'histoire est égal au nombre de la liste de calques que vous avez traitées par le temps que vous avez déchiré en bas de la liste dans son ensemble, l'histoire de votre opération aura une longueur égale à celle de la liste.

C'est ce que l'histoire de l'itération d'une liste(ory) ressemble à:

data History a b = Ancient b | Age a b (History a b)

History est une liste de paires de choses et de résultats, avec un supplément de résultat à la fin correspondant à l' []-chose. Nous allons paire de chaque couche de la liste d'entrée avec ses résultats correspondants.

cataL = foldr

history :: (a -> History a b -> b) -> b -> [a] -> History a b
history f z = cataL (\x h -> Age x (f x h) h) (Ancient z)

Une fois que vous avez plié vers le haut l'ensemble de la liste de droite à gauche, le résultat final sera en haut de la pile.

headH :: History a b -> b
headH (Ancient x) = x
headH (Age _ x _) = x

histoL :: (a -> History a b -> b) -> b -> [a] -> b
histoL f z = headH . history f z

(Il arrive que l' History a est un comonad, mais headH (née extract) est tout que nous avons besoin de définir histoL.)


History étiquettes de chaque couche de la liste d'entrée avec ses résultats correspondants. Le cofree comonad capte le modèle de l'étiquetage de chaque couche de la structure arbitraire.

data Cofree f a = Cofree { headC :: a, tailC :: f (Cofree f a) }

(Je suis venu avec History en branchant ListF en Cofree et la simplification.)

A comparer avec le libre monade,

data Free f a = Free (f (Free f a))
              | Return a

Free est un coproduct type; Cofree est un type de produit. Free couches une lasagne de fs, avec des valeurs a au bas de la lasagne. Cofree couches de la lasagne avec des valeurs a au niveau de chaque couche. Gratuit monades sont généralisées à l'extérieur-étiquetés arbres; cofree comonads sont généralisées à l'interne-arbres étiquetés.

Avec Cofree à la main, on peut généraliser à partir de listes à la fixpoint de l'arbitraire d'un foncteur,

newtype Fix f = Fix { unFix :: f (Fix f) }

cata :: Functor f => (f b -> b) -> Fix f -> b
cata f = f . fmap (cata f) . unFix

histo :: Functor f => (f (Cofree f b) -> b) -> Fix f -> b
histo f = headC . cata (\x -> Cofree (f x) x)

et une fois de plus récupérer la version de liste.

data ListF a r = Nil_ | Cons_ a r deriving Functor
type List a = Fix (ListF a)
type History' a b = Cofree (ListF a) b

histoL' :: (a -> History' a b -> b) -> b -> List a -> b
histoL' f z = histo g
    where g Nil_ = z
          g (Cons_ x h) = f x h

De côté: histo ) est le double de l' futu. Regardez leurs types.

histo :: Functor f => (f (Cofree f a) -> a) -> (Fix f -> a)
futu  :: Functor f => (a  ->  f (Free f a)) -> (a -> Fix f)

futu est histo avec les flèches inversées et avec Free remplacé par Cofree. Histomorphisms voir le passé; futumorphisms prédire l'avenir. Et à l'instar d' cata f . ana g peuvent être fusionnées en un hylomorphism, histo f . futu g peuvent être fusionnées en un chronomorphism.

Même si vous ignorez les mathsy parties, ce document de Hinze et Wu dispose d'un bon exemple-driven tutoriel sur la histomorphisms et de leur utilisation.

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