Quelqu'un peut-il expliquer comment fonctionne foldr
?
Prenez ces exemples:
Prelude> foldr (-) 54 [10, 11]
53
Prelude> foldr (\x y -> (x+y)/2) 54 [12, 4, 10, 6]
12.0
Je suis confus au sujet de ces exécutions. Aucune suggestion?
foldr
commence à l'extrémité droite de la liste et combine chaque entrée de liste avec la valeur de l'accumulateur en utilisant la fonction que vous lui donnez. Le résultat est la valeur finale de l'accumulateur après "pliage" dans tous les éléments de la liste. Son type est:
foldr :: (a -> b -> b) -> b -> [a] -> b
et à partir de cela, vous pouvez voir que l'élément list (de type a
) est le premier argument de la fonction donnée, et l'accumulateur (de type b
) est le second.
Pour votre premier exemple:
Starting accumulator = 54
11 - 54 = -43
10 - (-43) = 53
^ Result from the previous line
^ Next list item
Donc, la réponse que vous avez obtenue était 53.
Le deuxième exemple:
Starting accumulator = 54
(6 + 54) / 2 = 30
(10 + 30) / 2 = 20
(4 + 20) / 2 = 12
(12 + 12) / 2 = 12
Le résultat est donc 12.
Edit: je voulais ajouter, c'est pour les listes finies. foldr
peut également fonctionner sur des listes infinies, mais je pense qu'il est préférable de se familiariser d'abord avec le cas fini.
La façon la plus simple de comprendre foldr est de réécrire la liste que vous pliez sans le sucre.
[1,2,3,4,5] => 1:(2:(3:(4:(5:[]))))
maintenant quoi foldr f x
fait, c'est qu'il remplace chaque :
avec f
sous forme d'infixe et []
avec x
et évalue le résultat.
Par exemple:
sum [1,2,3] = foldr (+) 0 [1,2,3]
[1,2,3] === 1:(2:(3:[]))
donc
sum [1,2,3] === 1+(2+(3+0)) = 6
Il aide à comprendre la distinction entre foldr
et foldl
. Pourquoi foldr
est-il appelé "repli vers la droite"?
Au départ, je pensais que c'était parce qu'il consommait des éléments de droite à gauche. Pourtant, foldr
et foldl
consomment la liste de gauche à droite.
foldl
évalue de gauche à droite (gauche-associatif)foldr
évalue de droite à gauche (associative droite)Nous pouvons clarifier cette distinction avec un exemple qui utilise un opérateur pour lequel l'associativité est importante. Nous pourrions utiliser un exemple humain, tel que l'opérateur, "mange":
foodChain = (human : (shark : (fish : (algae : []))))
foldl step [] foodChain
where step eater food = eater `eats` food -- note that "eater" is the accumulator and "food" is the element
foldl `eats` [] (human : (shark : (fish : (algae : []))))
== foldl eats (human `eats` shark) (fish : (algae : []))
== foldl eats ((human `eats` shark) `eats` fish) (algae : [])
== foldl eats (((human `eats` shark) `eats` fish) `eats` algae) []
== (((human `eats` shark) `eats` fish) `eats` algae)
La sémantique de ce foldl
est: Un humain mange du requin, puis le même humain qui a mangé du requin mange ensuite du poisson, etc. Le mangeur est l'accumulateur.
Comparez cela avec:
foldr step [] foodChain
where step food eater = eater `eats` food. -- note that "eater" is the element and "food" is the accumulator
foldr `eats` [] (human : (shark : (fish : (algae : []))))
== foldr eats (human `eats` shark) (fish : (algae : []))))
== foldr eats (human `eats` (shark `eats` (fish)) (algae : [])
== foldr eats (human `eats` (shark `eats` (fish `eats` algae))) []
== (human `eats` (shark `eats` (fish `eats` algae)
La sémantique de ce foldr
est: Un humain mange un requin qui a déjà mangé un poisson, qui a déjà mangé des algues. La nourriture est l'accumulateur.
foldl
et foldr
mangent "décoller" de gauche à droite, ce n'est donc pas la raison pour laquelle nous appelons foldl "pli gauche". Au lieu de cela, l'ordre d'évaluation est important.
Pensez à foldr
très définition :
-- if the list is empty, the result is the initial value z
foldr f z [] = z
-- if not, apply f to the first element and the result of folding the rest
foldr f z (x:xs) = f x (foldr f z xs)
Ainsi, par exemple, foldr (-) 54 [10,11]
doit être égal à (-) 10 (foldr (-) 54 [11])
, C'est-à-dire à nouveau en expansion, égal à (-) 10 ((-) 11 54)
. Ainsi, l'opération interne est 11 - 54
, C'est-à-dire -43; et l'opération externe est 10 - (-43)
, c'est-à-dire 10 + 43
, donc 53
comme vous le constatez. Suivez les étapes similaires pour votre deuxième cas, et vous verrez à nouveau comment le résultat se forme!
foldr
signifie repli à partir de la droite, donc foldr (-) 0 [1, 2, 3]
produit (1 - (2 - (3 - 0)))
. En comparaison, foldl
produit (((0 - 1) - 2) - 3)
.
Lorsque les opérateurs ne sont pas commutatifsfoldl
et foldr
obtiendront des résultats différents.
Dans votre cas, le premier exemple se développe en (10 - (11 - 54))
ce qui donne 53.
Un moyen simple de comprendre foldr
est le suivant: il remplace chaque constructeur de liste par une application de la fonction fournie. Votre premier exemple se traduirait par:
10 - (11 - 54)
de:
10 : (11 : [])
Un bon conseil que j'ai obtenu du Wikibook Haskell pourrait être utile ici:
En règle générale, vous devez utiliser
foldr
sur des listes qui peuvent être infinies ou dans lesquelles le pli constitue une structure de données, etfoldl'
si la liste est connue pour être finie et se résume à une seule valeur.foldl
(sans la coche) devrait rarement être utilisé du tout.
J'ai toujours pensé http://foldr.com pour être une illustration amusante. Voir le Lambda the Ultimate post.
Je pense que la mise en œuvre simple de map, foldl et foldr aide à expliquer leur fonctionnement. Les exemples travaillés aident également à notre compréhension.
myMap f [] = []
myMap f (x:xs) = f x : myMap f xs
myFoldL f i [] = i
myFoldL f i (x:xs) = myFoldL f (f i x) xs
> tail [1,2,3,4] ==> [2,3,4]
> last [1,2,3,4] ==> 4
> head [1,2,3,4] ==> 1
> init [1,2,3,4] ==> [1,2,3]
-- where f is a function,
-- acc is an accumulator which is given initially
-- l is a list.
--
myFoldR' f acc [] = acc
myFoldR' f acc l = myFoldR' f (f acc (last l)) (init l)
myFoldR f z [] = z
myFoldR f z (x:xs) = f x (myFoldR f z xs)
> map (\x -> x/2) [12,4,10,6] ==> [6.0,2.0,5.0,3.0]
> myMap (\x -> x/2) [12,4,10,6] ==> [6.0,2.0,5.0,3.0]
> foldl (\x y -> (x+y)/2) 54 [12, 4, 10, 6] ==> 10.125
> myFoldL (\x y -> (x+y)/2) 54 [12, 4, 10, 6] ==> 10.125
foldl from above: Starting accumulator = 54
(12 + 54) / 2 = 33
(4 + 33) / 2 = 18.5
(10 + 18.5) / 2 = 14.25
(6 + 14.25) / 2 = 10.125`
> foldr (++) "5" ["1", "2", "3", "4"] ==> "12345"
> foldl (++) "5" ["1", "2", "3", "4"] ==> “51234"
> foldr (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
> myFoldR' (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
> myFoldR (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
foldr from above: Starting accumulator = 54
(6 + 54) / 2 = 30
(10 + 30) / 2 = 20
(4 + 20) / 2 = 12
(12 + 12) / 2 = 12
Ok, regardons les arguments:
valeur de retour:
Il applique d'abord la fonction au dernier élément de la liste et au résultat de la liste vide. Il réapplique ensuite la fonction avec ce résultat et l'élément précédent, et ainsi de suite jusqu'à ce qu'il prenne un résultat en cours et le premier élément de la liste pour renvoyer le résultat final.
Pliez "replie" une liste autour d'un résultat initial en utilisant une fonction qui prend un élément et un résultat de pliage précédent. Il répète cela pour chaque élément. Donc, foldr fait cela en commençant à la fin de la liste, ou du côté droit de celle-ci.
folr f emptyresult [1,2,3,4]
Se transforme en f(1, f(2, f(3, f(4, emptyresult) ) ) )
. Maintenant, suivez simplement les parenthèses dans l'évaluation et c'est tout.
Une chose importante à noter est que la fonction fournie f
doit gérer sa propre valeur de retour comme deuxième argument, ce qui implique que les deux doivent avoir le même type.
Source: mon article où je le regarde d'un point de vue javascript impératif et incurvé si vous pensez que cela pourrait aider.