web-dev-qa-db-fra.com

Ecrire foldl en utilisant foldr

Dans Real World Haskell, Chapitre 4. Programmation fonctionnelle

Ecrire foldl avec foldr:

-- file: ch04/Fold.hs
myFoldl :: (a -> b -> a) -> a -> [b] -> a

myFoldl f z xs = foldr step id xs z
    where step x g a = g (f a x)

Le code ci-dessus m'a beaucoup troublé, et quelqu'un appelé dps l'a récrit avec un nom explicite pour le rendre un peu plus clair:

myFoldl stepL zeroL xs = (foldr stepR id xs) zeroL
where stepR lastL accR accInitL = accR (stepL accInitL lastL)

Quelqu'un d'autre, Jef G, a ensuite fait un excellent travail en donnant un exemple et en montrant le mécanisme sous-jacent pas à pas:

myFoldl (+) 0 [1, 2, 3]
= (foldR step id [1, 2, 3]) 0
= (step 1 (step 2 (step 3 id))) 0
= (step 1 (step 2 (\a3 -> id ((+) a3 3)))) 0
= (step 1 (\a2 -> (\a3 -> id ((+) a3 3)) ((+) a2 2))) 0
= (\a1 -> (\a2 -> (\a3 -> id ((+) a3 3)) ((+) a2 2)) ((+) a1 1)) 0
= (\a1 -> (\a2 -> (\a3 -> (+) a3 3) ((+) a2 2)) ((+) a1 1)) 0
= (\a1 -> (\a2 -> (+) ((+) a2 2) 3) ((+) a1 1)) 0
= (\a1 -> (+) ((+) ((+) a1 1) 2) 3) 0
= (+) ((+) ((+) 0 1) 2) 3
= ((0 + 1) + 2) + 3

Mais je ne peux toujours pas comprendre cela, voici mes questions:

  1. A quoi sert la fonction id? Quel est le rôle de? Pourquoi devrions-nous en avoir besoin ici?
  2. Dans l'exemple ci-dessus, la fonction id est l'accumulateur dans la fonction lambda?
  3. Le prototype de foldr est foldr :: (a -> b -> b) -> b -> [a] -> b, et le premier paramètre est une fonction qui nécessite deux paramètres, mais la fonction step de l'implémentation de myFoldl utilise 3 paramètres. Je suis complètement confus!

Est-ce qu'il y a quelqu'un qui peut m'aider? Merci beaucoup!

69
ylzhang

Quelques explications sont en ordre!

A quoi sert la fonction id? Quel est le rôle de? Pourquoi devrions-nous en avoir besoin ici?

id est le fonction d'identité , id x = x, et est utilisé comme équivalent à zéro lors de la création d'une chaîne de fonctions avec composition de fonctions , (.). Vous pouvez le trouver défini dans le prélude .

Dans l'exemple ci-dessus, id function est l'accumulateur de la fonction lambda?

L'accumulateur est une fonction en cours de création via une application de fonction répétée. Il n'y a pas de lambda explicite, puisque nous nommons l'accumulateur, step. Vous pouvez l'écrire avec un lambda si vous voulez:

foldl f a bs = foldr (\b g x -> g (f x b)) id bs a

Ou comme Graham Hutton écrirait :

5.1 L'opérateur foldl

Maintenant généralisons à partir de l'exemple suml et considérons l'opérateur standard foldl qui traite les éléments d'une liste dans un ordre de gauche à droite en utilisant une fonction f pour combiner des valeurs, et une valeur v comme valeur de départ:

foldl :: (β → α → β) → β → ([α] → β)
foldl f v [ ] = v
foldl f v (x : xs) = foldl f (f v x) xs

En utilisant cet opérateur, suml peut être redéfini simplement par suml = foldl (+) 0. Beaucoup d'autres fonctions peuvent être définies d'une manière simple en utilisant foldl. Par exemple, la fonction standard reverse peut être redéfinie à l'aide de foldl comme suit:

reverse :: [α] → [α]
reverse = foldl (λxs x → x : xs) [ ]

Cette définition est plus efficace que notre définition originale utilisant fold, car elle évite l’utilisation de l’opérateur d’ajout inefficace (++) pour les listes.

Une simple généralisation du calcul dans la section précédente pour la fonction suml montre comment redéfinir la fonction foldl en fonction de fold:

foldl f v xs = fold (λx g → (λa → g (f a x))) id xs v

En revanche, il n’est pas possible de redéfinir fold en termes de foldl, car foldl est strict à la fin de son argument de liste mais fold n’est pas . Il existe un certain nombre de "théorèmes de dualité" utiles concernant fold et foldl, ainsi que quelques directives pour choisir l’opérateur le mieux adapté à des applications particulières (Bird, 1998).

Le prototype de est foldr :: (a -> b -> b) -> b -> [a] -> b .

Un programmeur Haskell dirait que le type de foldr est (a -> b -> b) -> b -> [a] -> b.

et le premier paramètre est une fonction qui nécessite deux paramètres, mais la fonction step de l'implémentation de myFoldl utilise trois paramètres. Je suis complètement confus

C'est déroutant et magique! Nous jouons un tour et remplaçons l'accumulateur par une fonction, qui est à son tour appliquée à la valeur initiale pour produire un résultat.

Graham Hutton explique le truc pour transformer foldl en foldr dans l'article ci-dessus. Nous commençons par écrire une définition récursive de foldl:

foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v []       = v
foldl f v (x : xs) = foldl f (f v x) xs

Et refactorisez-le via la transformation d'argument statique sur f:

foldl :: (a -> b -> a) -> a -> [b] -> a    
foldl f v xs = g xs v
    where
        g []     v = v
        g (x:xs) v = g xs (f v x)

Réécrivons maintenant g de manière à faire flotter les v intérieurs:

foldl f v xs = g xs v
    where
        g []     = \v -> v
        g (x:xs) = \v -> g xs (f v x)

Ce qui revient à penser que g en fonction d'un argument renvoie une fonction:

foldl f v xs = g xs v
    where
        g []     = id
        g (x:xs) = \v -> g xs (f v x)

Maintenant nous avons g, une fonction qui parcourt une liste de manière récursive, applique une fonction f. La valeur finale est la fonction d'identité, et chaque étape entraîne également une fonction.

Mais , nous avons déjà en main une fonction récursive très similaire sur les listes, foldr!

2 L'opérateur de pliage

L'opérateur fold tire ses origines de la théorie de la récursion (Kleene, 1952), tandis que l'utilisation de fold en tant que concept central dans un langage de programmation remonte à l'opérateur de réduction d'APL (Iverson, 1962). et plus tard à l'opérateur d'insertion de FP (Backus, 1978). Dans Haskell, l'opérateur fold pour les listes peut être défini comme suit:

fold :: (α → β → β) → β → ([α] → β)
fold f v [ ] = v
fold f v (x : xs) = f x (fold f v xs)

C'est-à-dire, étant donné une fonction f de type α → β → β et une valeur v de type β, la fonction fold f v traite une liste de type [α] pour donner une valeur de type β en remplaçant le constructeur nil [] à la fin de la liste par la valeur v, et chaque constructeur contre (:) dans la liste par la fonction f. De cette manière, l'opérateur fold encapsule un modèle simple de récursivité pour le traitement des listes, dans lequel les deux constructeurs des listes sont simplement remplacés par d'autres valeurs et fonctions. Un certain nombre de fonctions familières sur les listes ont une définition simple utilisant fold.

Cela ressemble à un schéma récursif très similaire à notre fonction g. Maintenant, le truc: en utilisant toute la magie disponible (aka Bird, Meertens et Malcolm), nous appliquons une règle spéciale, la propriété universelle de fold , qui est une équivalence entre deux définitions pour une fonction g qui traite des listes, ainsi:

g [] = v
g (x:xs) = f x (g xs)

si et seulement si

g = fold f v

Ainsi, la propriété universelle des plis stipule que:

    g = foldr k v

g doit être équivalent aux deux équations, pour certaines k et v:

    g []     = v
    g (x:xs) = k x (g xs)

De nos précédentes conceptions de foldl, nous connaissons v == id. Pour la deuxième équation cependant, nous devons calculer la définition de k:

    g (x:xs)         = k x (g xs)        
<=> g (x:xs) v       = k x (g xs) v      -- accumulator of functions
<=> g xs (f v x)     = k x (g xs) v      -- definition of foldl
<=  g' (f v x)       = k x g' v          -- generalize (g xs) to g'
<=> k = \x g' -> (\a -> g' (f v x))      -- expand k. recursion captured in g'

En remplaçant nos définitions calculées de k et v, on obtient une définition de foldl comme suit:

foldl :: (a -> b -> a) -> a -> [b] -> a    
foldl f v xs =
    foldr
        (\x g -> (\a -> g (f v x)))
        id
        xs
        v

Le g récursif est remplacé par le combinateur foldr, et l'accumulateur devient une fonction construite via une chaîne de compositions de f à chaque élément de la liste, dans l'ordre inverse (on se plie donc à gauche au lieu de ).

Ceci est certainement un peu avancé, donc pour comprendre en profondeur cette transformation, la propriété universelle de folds , qui rend la transformation possible, je recommande le tutoriel de Hutton, lié ci-dessous.


Références

92
Don Stewart

Considérons le type de foldr:

foldr :: (b -> a -> a) -> a -> [b] -> a

Considérant que le type de step est quelque chose comme b -> (a -> a) -> a -> a. Etant donné que l'étape est passée à foldr, nous pouvons conclure que, dans ce cas, le pli a un type comme (b -> (a -> a) -> (a -> a)) -> (a -> a) -> [b] -> (a -> a).

Ne soyez pas dérouté par les différentes significations de a dans différentes signatures; c'est juste une variable de type. Aussi, gardez à l'esprit que la flèche de fonction est associative à droite, donc a -> b -> c est la même chose que a -> (b -> c)

Donc, oui, la valeur de l'accumulateur pour foldr est une fonction de type a -> a et la valeur initiale est id. Cela a du sens, car id est une fonction qui ne fait rien - c'est la même raison pour laquelle vous commenceriez avec zéro comme valeur initiale lors de l'ajout de toutes les valeurs d'une liste.

En ce qui concerne step en prenant trois arguments, essayez de le récrire comme ceci:

step :: b -> (a -> a) -> (a -> a)
step x g = \a -> g (f a x)

Est-ce que cela rend plus facile de voir ce qui se passe? Il faut un paramètre supplémentaire car il renvoie une fonction et les deux manières de l'écrire sont équivalentes. Notez également le paramètre supplémentaire après la foldr: (foldr step id xs) z. La partie entre parenthèses est le pli lui-même, qui renvoie une fonction, qui est ensuite appliquée à z.

9
C. A. McCann

(parcourez rapidement mes réponses [1] , [2] , [3] , [4] pour vous assurer de bien comprendre la syntaxe de Haskell, les fonctions d'ordre supérieur, currying, composition de fonction, opérateur $, opérateurs infixe/préfixe, sections et lambdas)

Propriété universelle du pli

Un fold est simplement une codification de certains types de récursion. Et la propriété d'universalité indique simplement que, si votre récursivité se conforme à une certaine forme, elle peut être transformée en repliement selon certaines règles formelles. Et inversement, chaque pli peut être transformé en une récursion de ce genre. Encore une fois, certaines récursions peuvent être traduites en plis qui donnent exactement la même réponse, d'autres non, et il existe une procédure exacte pour le faire.

Fondamentalement, si votre fonction récursive fonctionne sur des listes et ressemble à celle du à gauche, vous pouvez la transformer pour plier le à droite en substituant f et v à ce qui existe réellement.

g []     = v              ⇒
g (x:xs) = f x (g xs)     ⇒     g = foldr f v

Par exemple:

sum []     = 0   {- recursion becomes fold -}
sum (x:xs) = x + sum xs   ⇒     sum = foldr 0 (+)

Ici, v = 0 et sum (x:xs) = x + sum xs sont équivalents à sum (x:xs) = (+) x (sum xs), donc f = (+). 2 autres exemples

product []     = 1
product (x:xs) = x * product xs  ⇒  product = foldr 1 (*)

length []     = 0
length (x:xs) = 1 + length xs    ⇒  length = foldr (\_ a -> 1 + a) 0

Exercice:

  1. Implémentez map, filter, reverse, concat et concatMap de manière récursive, tout comme les fonctions ci-dessus du côté {gauche}.

  2. Convertissez ces 5 fonctions en foldr selon une formule ci-dessus, c’est-à-dire en substituant f et v dans la formule de pliage sur le droit.

Foldl via foldr

Comment écrire une fonction récursive qui additionne les nombres de gauche à droite?

sum [] = 0     -- given `sum [1,2,3]` expands into `(1 + (2 + 3))`
sum (x:xs) = x + sum xs

La première fonction récursive à trouver se développe complètement avant même que l’ajout ne commence, ce n’est pas ce dont nous avons besoin. Une approche consiste à créer une fonction récursive qui a accumulateur, qui additionne immédiatement des nombres à chaque étape (en savoir plus sur tail récursivité pour en savoir plus sur les stratégies de récursivité):

suml :: [a] -> a
suml xs = suml' xs 0
  where suml' [] n = n   -- auxiliary function
        suml' (x:xs) n = suml' xs (n+x)

Bon, arrête! Exécutez ce code dans GHCi et assurez-vous de bien comprendre son fonctionnement, puis procédez soigneusement et réfléchi. suml ne peut pas être redéfini avec un pli, mais suml' peut l'être.

suml' []       = v    -- equivalent: v n = n
suml' (x:xs) n = f x (suml' xs) n

suml' [] n = n de la définition de fonction, non? Et v = suml' [] de la formule de la propriété universelle. Ensemble, cela donne v n = n, une fonction qui renvoie immédiatement tout ce qu’elle reçoit: v = id. Calculons f:

suml' (x:xs) n = f x (suml' xs) n
-- expand suml' definition
suml' xs (n+x) = f x (suml' xs) n
-- replace `suml' xs` with `g`
g (n+x)        = f x g n

Donc, suml' = foldr (\x g n -> g (n+x)) id et, donc, suml = foldr (\x g n -> g (n+x)) id xs 0.

foldr (\x g n -> g (n + x)) id [1..10] 0 -- return 55

Maintenant, il suffit de généraliser, remplacer + par une fonction variable:

foldl f a xs = foldr (\x g n -> g (n `f` x)) id xs a
foldl (-) 10 [1..5] -- returns -5

Conclusion

Maintenant, lisez Graham Hutton Un tutoriel sur l’universalité et l’expressivité du repli . Prenez un stylo et du papier, essayez de comprendre tout ce qu'il écrit jusqu'à ce que vous obteniez la plupart des plis par vous-même. Ne pas transpirer si vous ne comprenez pas quelque chose, vous pouvez toujours revenir plus tard, mais ne tardez pas beaucoup non plus.

5
Mirzhan Irkegulov

Voici ma preuve que foldl peut être exprimé en termes de foldr, ce que je trouve assez simple à part le nom spaghetti que la fonction step introduit.

La proposition est que foldl f z xs est équivalent à

myfoldl f z xs = foldr step_f id xs z
        where step_f x g a = g (f a x)

La première chose importante à noter ici est que le côté droit de la première ligne est en fait évalué comme

(foldr step_f id xs) z

puisque foldr ne prend que trois paramètres. Cela laisse déjà entendre que la foldr calculera non pas une valeur mais une fonction curry, qui est ensuite appliquée à z. Il faut enquêter sur deux cas pour savoir si myfoldl est foldl:

  1. Cas de base: liste vide

      myfoldl f z []
    = foldr step_f id [] z    (by definition of myfoldl)
    = id z                    (by definition of foldr)
    = z
    
      foldl f z []
    = z                       (by definition of foldl)
    
  2. Liste non vide

      myfoldl f z (x:xs)
    = foldr step_f id (x:xs) z          (by definition of myfoldl)
    = step_f x (foldr step_f id xs) z   (-> apply step_f)
    = (foldr step_f id xs) (f z x)      (-> remove parentheses)
    = foldr step_f id xs (f z x)
    = myfoldl f (f z x) xs              (definition of myfoldl)
    
      foldl f z (x:xs)
    = foldl f (f z x) xs
    

Comme dans 2. la première et la dernière ligne ont la même forme dans les deux cas, il est possible de replier la liste jusqu'à xs == [], auquel cas 1. garantit le même résultat. Donc, par induction, myfoldl == foldl.

4
David

Il n’ya pas de voie royale vers les mathématiques, ni même par Haskell. Laisser

h z = (foldr step id xs) z where   
     step x g =  \a -> g (f a x)

Qu'est-ce que c'est que h z? Supposons que xs = [x0, x1, x2].
Appliquer la définition de foldr: 

h z = (step x0 (step x1 (step x2 id))) z 

Appliquer la définition de l'étape:

= (\a0 -> (\a1 -> (\a2 -> id (f a2 x2)) (f a1 x1)) (f a0 x0)) z

Remplacez les fonctions lambda:

= (\a1 -> (\a2 -> id (f a2 x2)) (f a1 x1)) (f z x0)

= (\a2 -> id (f a2 x2)) (f (f z x0) x1)

= id (f (f (f z x0) x1) x2)

Appliquer la définition de l'identifiant: 

= f (f (f z x0) x1) x2

Appliquer la définition de foldl:

= foldl f z [x0, x1, x2]

Est-ce une route royale ou quoi?

1
disznoperzselo

Cela pourrait aider, j'ai essayé de développer d'une manière différente. 

myFoldl (+) 0 [1,2,3] = 
foldr step id [1,2,3] 0 = 
foldr step (\a -> id (a+3)) [1,2] 0 = 
foldr step (\b -> (\a -> id (a+3)) (b+2)) [1] 0 = 
foldr step (\b -> id ((b+2)+3)) [1] 0 = 
foldr step (\c -> (\b -> id ((b+2)+3)) (c+1)) [] 0 = 
foldr step (\c -> id (((c+1)+2)+3)) [] 0 = 
(\c -> id (((c+1)+2)+3)) 0 = ...
1
Dulguun Otgon
foldr step zero (x:xs) = step x (foldr step zero xs)
foldr _ zero []        = zero

myFold f z xs = foldr step id xs z
  where step x g a = g (f a x)

myFold (+) 0 [1, 2, 3] =
  foldr step id [1, 2, 3] 0
  -- Expanding foldr function
  step 1 (foldr step id [2, 3]) 0
  step 1 (step 2 (foldr step id [3])) 0
  step 1 (step 2 (step 3 (foldr step id []))) 0
  -- Expanding step function if it is possible
  step 1 (step 2 (step 3 id)) 0
  step 2 (step 3 id) (0 + 1)
  step 3 id ((0 + 1) + 2)
  id (((0 + 1) + 2) + 3)

Eh bien, au moins, cela m'a aidé. Même ce n'est pas tout à fait correct.

0
hanrai