- 1. Based on definitions from Left and Right Folds Comparison of a mathematical definition and a programmatic one Polyglot FP for Fun and Profit - Haskell and Scala @philip_schwarz slides by https://www.slideshare.net/pjschwarz Sergei Winitzki sergei-winitzki-11a6431 Richard Bird http://www.cs.ox.ac.uk/people/richard.bird/
- 2. ๐๐๐๐๐ โท ๐ฝ โ ๐ผ โ ๐ฝ โ ๐ฝ โ ๐ผ โ ๐ฝ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ โถ ๐ฅ๐ = ๐๐๐๐๐ ๐ ๐ ๐ ๐ฅ ๐ฅ๐ ๐๐๐๐๐ โท ๐ผ โ ๐ฝ โ ๐ฝ โ ๐ฝ โ ๐ผ โ ๐ฝ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ โถ ๐ฅ๐ = ๐ ๐ฅ ๐๐๐๐๐ ๐ ๐ ๐ฅ๐ If I have to write down the definitions of a left fold and a right fold for lists, here is what I write While both definitions are recursive, the left fold is tail recursive, whereas the right fold isnโt. Although I am very familiar with the above definitions, and view them as doing a good job of explaining the two folds, I am always interested in alternative ways of explaining things, and so I have been looking at Sergei Winitzkiโs mathematical definitions of left and right folds, in his upcoming book: The Science of Functional Programming (SOFP). Sergeiโs definitions of the folds are in the top two rows of the following table Sergei Winitzki Richard Bird @philip_schwarz
- 3. test1 = TestCase (assertEqual "foldl(+) 0 []" 0 (foldl (+) 0 [])) test2 = TestCase (assertEqual "foldl(+) 0 [1,2,3,4]" 10 (foldl (+) 0 [1,2,3,4])) test3 = TestCase (assertEqual "foldr (+) 0 []" 0 (foldr (+) 0 [])) test4 = TestCase (assertEqual "foldr (+) 0 [1,2,3,4]" 10 (foldr (+) 0 [1,2,3,4])) Left folds and right folds do not necessarily produce the same results. According to the first duality theorem of folding, one case in which the results are the same, is when we fold using the unit and associative operation of a monoid. First duality theorem. Suppose โ is associative with unit ๐. Then ๐๐๐๐๐ โ ๐ ๐ฅ๐ = ๐๐๐๐๐ โ ๐ ๐ฅ๐ For all finite lists ๐ฅ๐ . test5 = TestCase (assertEqual "foldl(-) 0 []" 0 (foldl (+) 0 [])) test6 = TestCase (assertEqual "foldl(-) 0 [1,2,3,4]" (- 10) (foldl (-) 0 [1,2,3,4])) test7 = TestCase (assertEqual "foldr (-) 0 []" 0 (foldr (-) 0 [])) test8 = TestCase (assertEqual "foldr (-) 0 [1,2,3,4]" (- 2) (foldr (-) 0 [1,2,3,4])) Folding integers left and right using the (Int,+,0) monoid, for example, produces the same results. But folding integers left and right using subtraction and zero, does not produce the same results, and in fact (Int,-,0) is not a monoid, because subtraction is not associative.
- 4. ๐ = ๐; ๐ ๐ฅ โงบ ๐ = ๐(๐ฅ, ๐(๐ )) ๐ = ๐; ๐ ๐ โงบ [๐ฅ] = ๐(๐ ๐ , ๐ฅ) To avoid any confusion (the definitions use the same function name ๐), and to align with the definitions on the right, letโs modify Sergeiโs definitions by doing some simple renaming. Here are Sergeiโs mathematical definitions again, on the right (the โงบ operator is list concatenation). Notice how neither definition is tail recursive. That is deliberate. As Sergei explained to me: โI'd like to avoid putting tail recursion into a mathematical formula, because tail recursion is just a detail of how we implement this functionโ and โThe fact that foldLeft is tail recursive for List is an implementation detail that is specific to the List type. It will be different for other sequence types. I do not want to put the implementation details into the formulas.โ ๐ โ ๐๐๐๐๐ ๐ โ ๐ ๐ โ ๐ ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐ฅ โงบ ๐ = ๐(๐ฅ, ๐๐๐๐๐(๐ )) ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐ โงบ [๐ฅ] = ๐(๐๐๐๐๐ ๐ , ๐ฅ) ๐ โ ๐๐๐๐๐ ๐ โ ๐ ๐ โ ๐ ๐๐๐๐๐ โท ๐ฝ โ ๐ผ โ ๐ฝ โ ๐ฝ โ ๐ผ โ ๐ฝ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ โถ ๐ฅ๐ = ๐๐๐๐๐ ๐ ๐ ๐ ๐ฅ ๐ฅ๐ ๐๐๐๐๐ โท ๐ผ โ ๐ฝ โ ๐ฝ โ ๐ฝ โ ๐ผ โ ๐ฝ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ โถ ๐ฅ๐ = ๐ ๐ฅ ๐๐๐๐๐ ๐ ๐ ๐ฅ๐ ๐ = ๐; ๐ ๐ฅ โงบ ๐ = ๐(๐ฅ, ๐(๐ )) ๐ = ๐; ๐ ๐ โงบ [๐ฅ] = ๐(๐ ๐ , ๐ฅ)
- 5. ๐๐๐๐๐ ๐ ๐ [ ] = ๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ โงบ ๐ = ๐ ๐ฅ (๐๐๐๐๐ ๐ ๐ ๐ ) ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐ โงบ [๐ฅ] = ๐ ๐๐๐๐๐ ๐ ๐ ๐ ๐ฅ ๐๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ๐ โ[๐] โ ๐ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐๐ = ๐ ๐๐๐๐๐ ๐ ๐ (๐๐๐๐ก ๐๐ ) (๐๐๐ ๐ก ๐๐ ) ๐๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ๐ โ[๐] โ ๐ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐๐ = ๐ โ๐๐๐ ๐๐ ๐๐๐๐๐ ๐ ๐ (๐ก๐๐๐ ๐๐ ) ๐๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ๐ โ[๐] โ ๐ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐๐ = ๐ ๐ ๐ ๐คโ๐๐๐ ๐ = ๐๐๐ ๐ก ๐๐ ๐ = ๐๐๐๐๐ ๐ ๐ (๐๐๐๐ก ๐๐ ) ๐๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ๐ โ[๐] โ ๐ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐๐ = ๐ ๐ ๐ ๐คโ๐๐๐ ๐ = โ๐๐๐ ๐๐ ๐ = ๐๐๐๐๐ ๐ ๐ (๐ก๐๐๐ ๐๐ ) ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐ฅ โงบ ๐ = ๐(๐ฅ, ๐๐๐๐๐(๐ )) ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐ โงบ [๐ฅ] = ๐(๐๐๐๐๐ ๐ , ๐ฅ) To help us understand the above two definitions, letโs first express them in Haskell, pretending that we are able to use ๐ โงบ [๐ฅ] and ๐ฅ โงบ ๐ in a pattern match. Now letโs replace ๐ โงบ [๐ฅ] and ๐ฅ โงบ ๐ with as, and get the functions to extract the ๐ and the ๐ฅ using the โ๐๐๐, ๐ก๐๐๐, ๐๐๐ ๐ก and ๐๐๐๐ก. Letโs also add type signatures And now letโs make it more obvious that what each fold is doing is taking an ๐ from the list, folding the rest of the list into a ๐, and then returning the result of calling ๐ with ๐ and ๐.
- 6. ๐๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ๐ โ[๐] โ ๐ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐๐ = ๐ ๐ ๐ ๐คโ๐๐๐ ๐ = ๐๐๐ ๐ก ๐๐ ๐ = ๐๐๐๐๐ ๐ ๐ (๐๐๐๐ก ๐๐ ) ๐๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ๐ โ[๐] โ ๐ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐๐ = ๐ ๐ ๐ ๐คโ๐๐๐ ๐ = โ๐๐๐ ๐๐ ๐ = ๐๐๐๐๐ ๐ ๐ (๐ก๐๐๐ ๐๐ ) Since the above two functions are very similar, letโs extract their common logic into a fold function. Letโs call the function that is used to extract an element from the list ๐ก๐๐๐, and the function that is used to extract the rest of the list ๐๐๐ ๐ก. ๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ([๐] โ ๐) โ ([๐] โ [๐]) โ ๐ โ [๐] โ ๐ ๐๐๐๐ ๐ ๐ก๐๐๐ ๐๐๐ ๐ก ๐ = ๐ ๐๐๐๐ ๐ ๐ก๐๐๐ ๐๐๐ ๐ก ๐ ๐๐ = ๐ ๐ ๐ ๐คโ๐๐๐ ๐ = ๐ก๐๐๐ ๐๐ ๐ = ๐๐๐๐ ๐ ๐ก๐๐๐ ๐๐๐ ๐ก ๐ (๐๐๐ ๐ก ๐๐ ) We can now define left and right folds in terms of ๐๐๐๐. ๐๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ๐ โ[๐] โ ๐ ๐๐๐๐๐ ๐ = ๐๐๐๐ ๐ ๐๐๐ ๐ก ๐๐๐๐ก ๐๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ๐ โ[๐] โ ๐ ๐๐๐๐๐ ๐ = ๐๐๐๐ ๐๐๐๐ ๐ โ๐๐๐ ๐ก๐๐๐ ๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ๐ โ ๐ โ ๐ ๐๐๐๐ ๐ ๐ฅ ๐ฆ = ๐ ๐ฆ ๐ฅ The slightly perplexing thing is that while a left fold applies ๐ to list elements starting with the โ๐๐๐ of the list and proceeding from left to right, the above ๐๐๐๐๐ function achieves that by navigating through list elements from right to left. Third duality theorem. For all finite lists ๐ฅ๐ , ๐๐๐๐๐ ๐ ๐ ๐ฅ๐ = ๐๐๐๐๐ ๐๐๐๐ ๐ ๐ (๐๐๐ฃ๐๐๐ ๐ ๐ฅ๐ ) ๐๐๐๐๐ ๐๐๐๐ ๐ ๐ฅ ๐ฆ = ๐ ๐ฆ ๐ฅ The fact that we can define ๐๐๐๐๐ and ๐๐๐๐๐ in terms of ๐๐๐๐, as we do above, seems related to the third duality theorem of folding. Instead of our ๐๐๐๐๐ function being passed the reverse of the list passed to ๐๐๐๐๐, it processes the list with ๐๐๐ ๐ก and ๐๐๐๐ก, rather than with โ๐๐๐ and ๐ก๐๐๐. @philip_schwarz
- 7. To summarise what we did to help understand SOFPโs mathematical definitions of left and right fold: we turned them into code and expressed them in terms of a common function ๐๐๐๐ that uses a ๐ก๐๐๐ function to extract an element from the list being folded, and a ๐๐๐ ๐ก function to extract the rest of the list. ๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ([๐] โ ๐) โ ([๐] โ [๐]) โ ๐ โ [๐] โ ๐ ๐๐๐๐ ๐ ๐ก๐๐๐ ๐๐๐ ๐ก ๐ = ๐ ๐๐๐๐ ๐ ๐ก๐๐๐ ๐๐๐ ๐ก ๐ ๐๐ = ๐ ๐ ๐ ๐คโ๐๐๐ ๐ = ๐ก๐๐๐ ๐๐ ๐ = ๐๐๐๐ ๐ ๐ก๐๐๐ ๐๐๐ ๐ก ๐ (๐๐๐ ๐ก ๐๐ ) ๐๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ๐ โ[๐] โ ๐ ๐๐๐๐๐ ๐ = ๐๐๐๐ ๐ ๐๐๐ ๐ก ๐๐๐๐ก ๐๐๐๐๐ :: (๐ โ ๐ โ ๐) โ ๐ โ[๐] โ ๐ ๐๐๐๐๐ ๐ = ๐๐๐๐ ๐๐๐๐ ๐ โ๐๐๐ ๐ก๐๐๐ ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐ฅ โงบ ๐ = ๐(๐ฅ, ๐๐๐๐๐(๐ )) ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐ โงบ [๐ฅ] = ๐(๐๐๐๐๐ ๐ , ๐ฅ) ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐๐ = ๐(โ๐๐๐(๐๐ ), ๐๐๐๐๐(๐ก๐๐๐(๐๐ ))) ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐๐ = ๐(๐๐๐๐๐ ๐๐๐๐ก(๐๐ ) , ๐๐๐ ๐ก(๐๐ )) Letโs feed one aspect of the above back into Sergeiโs definitions. Letโs temporarily rewrite them by replacing ๐ โงบ [๐ฅ] and ๐ฅ โงบ ๐ with as, and getting the definitions to extract the ๐ and the ๐ฅ using the functions โ๐๐๐, ๐ก๐๐๐, ๐๐๐ ๐ก and ๐๐๐๐ก. Notice how the flipping of ๐ done by the ๐๐๐๐๐ function above, is reflected, in the ๐๐๐๐๐ function below, in the fact that its ๐ takes an ๐ and a ๐, rather than a ๐ and an ๐.
- 8. Another thing we can do to understand SOFPโs definitions of left and right folds, is to see how they work when applied to a sample list, e.g. [๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3], when we run them manually. In the next slide we first do this for the following definitions ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐ โงบ [๐ฅ] = ๐(๐๐๐๐๐ ๐ , ๐ฅ) ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐ฅ โงบ ๐ = ๐(๐ฅ, ๐๐๐๐๐(๐ )) ๐๐๐๐๐ โท ๐ฝ โ ๐ผ โ ๐ฝ โ ๐ฝ โ ๐ผ โ ๐ฝ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ โถ ๐ฅ๐ = ๐๐๐๐๐ ๐ ๐ ๐ ๐ฅ ๐ฅ๐ ๐๐๐๐๐ โท ๐ผ โ ๐ฝ โ ๐ฝ โ ๐ฝ โ ๐ผ โ ๐ฝ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ โถ ๐ฅ๐ = ๐ ๐ฅ ๐๐๐๐๐ ๐ ๐ ๐ฅ๐ In the slide after that, we do it for SOFPโs definitions.
- 9. โถ / ๐ฅ0 โถ / ๐ฅ1 โถ / ๐ฅ2 โถ / ๐ฅ3 ๐ / ๐ ๐ฅ3 / ๐ ๐ฅ2 / ๐ ๐ฅ1 / ๐ ๐ฅ0 ๐ / ๐ฅ0 ๐ / ๐ฅ1 ๐ / ๐ฅ2 ๐ / ๐ฅ3 ๐ ๐ฅ0: (๐ฅ1: ๐ฅ2: ๐ฅ3: ) ๐ ๐ ๐ ๐ ๐ ๐ฅ0 ๐ฅ1 ๐ฅ2 ๐ฅ3 var ๐๐๐ = ๐ foreach(๐ฅ in ๐ฅs) ๐๐๐ = ๐(๐๐๐, ๐ฅ) return ๐๐๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ๐ ๐ฅ๐ = [๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3] ๐๐๐๐๐ โท ๐ผ โ ๐ฝ โ ๐ฝ โ ๐ฝ โ ๐ผ โ ๐ฝ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ: ๐ฅ๐ = ๐ ๐ฅ ๐๐๐๐๐ ๐ ๐ ๐ฅ๐ ๐๐๐๐๐ โท ๐ฝ โ ๐ผ โ ๐ฝ โ ๐ฝ โ ๐ผ โ ๐ฝ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ: ๐ฅ๐ = ๐๐๐๐๐ ๐ ๐ ๐ ๐ฅ ๐ฅ๐ ๐๐๐๐๐๐๐: โถ ๐ค๐๐กโ ๐ ๐ค๐๐กโ ๐ ๐๐๐๐๐ ๐ ๐ [๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3] ๐ ๐ฅ0 ๐๐๐๐๐ ๐ ๐ [๐ฅ1, ๐ฅ2, ๐ฅ3] ๐ ๐ฅ0 (๐ ๐ฅ1 (๐๐๐๐๐ ๐ ๐ [๐ฅ2, ๐ฅ3])) ๐ ๐ฅ0 (๐ ๐ฅ1 (๐ ๐ฅ2 (๐๐๐๐๐ ๐ ๐ [๐ฅ3]))) ๐ ๐ฅ0 (๐ ๐ฅ1 (๐ ๐ฅ2 (๐ ๐ฅ3 (๐๐๐๐๐ ๐ ๐ [ ])))) ๐ ๐ฅ0 (๐ ๐ฅ1 (๐ ๐ฅ2 (๐ ๐ฅ3 ๐))) ๐ ๐ฅ0 (๐ ๐ฅ1 (๐ ๐ฅ2 (๐ ๐ฅ3 ๐))) ๐๐๐๐๐ ๐ ๐ [๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3] ๐๐๐๐๐ ๐ ๐ ๐ ๐ฅ0 [๐ฅ1, ๐ฅ2, ๐ฅ3] ๐๐๐๐๐ ๐ ๐ ๐ ๐ ๐ฅ0 ๐ฅ1 [๐ฅ2, ๐ฅ3] ๐๐๐๐๐ ๐ ๐ ๐ ๐ ๐ ๐ฅ0 ๐ฅ1 ๐ฅ2 [๐ฅ3] ๐๐๐๐๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ฅ0 ๐ฅ1 ๐ฅ2 ๐ฅ3 [ ] ๐ ๐ ๐ ๐ ๐ ๐ฅ0 ๐ฅ1 ๐ฅ2 ๐ฅ3
- 10. โถ / ๐ฅ0 โถ / ๐ฅ1 โถ / ๐ฅ2 โถ / ๐ฅ3 ๐ / ๐ ๐ฅ3 / ๐ ๐ฅ2 / ๐ ๐ฅ1 / ๐ ๐ฅ0 ๐ / ๐ฅ0 ๐ / ๐ฅ1 ๐ / ๐ฅ2 ๐ / ๐ฅ3 ๐ ๐ฅ0: (๐ฅ1: ๐ฅ2: ๐ฅ3: ) ๐(๐ ๐ ๐ ๐, ๐ฅ0 , ๐ฅ1 , ๐ฅ2 , ๐ฅ3) ๐๐๐๐๐ ๐ฅ๐ ๐๐๐๐๐ ๐ฅ๐ ๐ฅ๐ = [๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3] ๐(๐ฅ0, ๐(๐ฅ1, ๐(๐ฅ2, ๐(๐ฅ3, ๐)))) ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐ โงบ [๐ฅ] = ๐(๐๐๐๐๐ ๐ , ๐ฅ) ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐ฅ โงบ ๐ = ๐(๐ฅ, ๐๐๐๐๐(๐ )) ๐๐๐๐๐ ๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3 , ๐ ๐๐๐๐๐ ๐ฅ0, ๐ฅ1, ๐ฅ2 , ๐ฅ3 ๐(๐(๐๐๐๐๐ [๐ฅ0, ๐ฅ1] , ๐ฅ2), ๐ฅ3) ๐ ๐ ๐ ๐๐๐๐๐ ๐ฅ0 , ๐ฅ1 , ๐ฅ2 , ๐ฅ3 ๐ ๐ ๐ ๐ ๐๐๐๐๐ [ ] , ๐ฅ0 , ๐ฅ1 , ๐ฅ2 , ๐ฅ3 ๐ ๐ ๐ ๐ ๐, ๐ฅ0 , ๐ฅ1 , ๐ฅ2 , ๐ฅ3 ๐๐๐๐๐([๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3]) ๐(๐ฅ0, ๐๐๐๐๐([๐ฅ1, ๐ฅ2, ๐ฅ3])) ๐(๐ฅ0, ๐(๐ฅ1, ๐๐๐๐๐([๐ฅ2, ๐ฅ3]))) ๐(๐ฅ0, ๐(๐ฅ1, ๐(๐ฅ2, ๐๐๐๐๐([๐ฅ3])))) ๐(๐ฅ0, ๐(๐ฅ1, ๐(๐ฅ2, ๐(๐ฅ3, ๐๐๐๐๐([]))))) ๐(๐ฅ0, ๐(๐ฅ1, ๐(๐ฅ2, ๐(๐ฅ3, ๐))))
- 11. ๐๐๐๐๐ โท ๐ฝ โ ๐ผ โ ๐ฝ โ ๐ฝ โ ๐ผ โ ๐ฝ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ: ๐ฅ๐ = ๐๐๐๐๐ ๐ ๐ ๐ ๐ฅ ๐ฅ๐ ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐ โงบ [๐ฅ] = ๐(๐๐๐๐๐ ๐ , ๐ฅ) โถ / ๐ฅ0 โถ / ๐ฅ1 โถ / ๐ฅ2 โถ / ๐ฅ3 ๐ฅ0: (๐ฅ1: ๐ฅ2: ๐ฅ3: ) ๐ฅ๐ = [๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3] ๐ / ๐ ๐ฅ3 / ๐ ๐ฅ2 / ๐ ๐ฅ1 / ๐ ๐ฅ0 ๐๐๐๐๐ ๐ ๐ [๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3] ๐๐๐๐๐ ๐ ๐ ๐ ๐ฅ0 [๐ฅ1, ๐ฅ2, ๐ฅ3] ๐๐๐๐๐ ๐ ๐ ๐ ๐ ๐ฅ0 ๐ฅ1 [๐ฅ2, ๐ฅ3] ๐๐๐๐๐ ๐ ๐ ๐ ๐ ๐ ๐ฅ0 ๐ฅ1 ๐ฅ2 [๐ฅ3] ๐๐๐๐๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ฅ0 ๐ฅ1 ๐ฅ2 ๐ฅ3 [ ] ๐ ๐ ๐ ๐ ๐ ๐ฅ0 ๐ฅ1 ๐ฅ2 ๐ฅ3 ๐๐๐๐๐ ๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3 , ๐ ๐๐๐๐๐ ๐ฅ0, ๐ฅ1, ๐ฅ2 , ๐ฅ3 ๐(๐(๐๐๐๐๐ [๐ฅ0, ๐ฅ1] , ๐ฅ2), ๐ฅ3) ๐ ๐ ๐ ๐๐๐๐๐ ๐ฅ0 , ๐ฅ1 , ๐ฅ2 , ๐ฅ3 ๐ ๐ ๐ ๐ ๐๐๐๐๐ [ ] , ๐ฅ0 , ๐ฅ1 , ๐ฅ2 , ๐ฅ3 ๐ ๐ ๐ ๐ ๐, ๐ฅ0 , ๐ฅ1 , ๐ฅ2 , ๐ฅ3 Now letโs compare the results for both definitions of ๐๐๐๐๐. The results are the same. @philip_schwarz
- 12. ๐ / ๐ฅ0 ๐ / ๐ฅ1 ๐ / ๐ฅ2 ๐ / ๐ฅ3 ๐ ๐๐๐๐๐ โท ๐ผ โ ๐ฝ โ ๐ฝ โ ๐ฝ โ ๐ผ โ ๐ฝ ๐๐๐๐๐ ๐ ๐ = ๐ ๐๐๐๐๐ ๐ ๐ ๐ฅ: ๐ฅ๐ = ๐ ๐ฅ ๐๐๐๐๐ ๐ ๐ ๐ฅ๐ ๐๐๐๐๐ ๐ ๐ [๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3] ๐ ๐ฅ0 ๐๐๐๐๐ ๐ ๐ [๐ฅ1, ๐ฅ2, ๐ฅ3] ๐ ๐ฅ0 (๐ ๐ฅ1 (๐๐๐๐๐ ๐ ๐ [๐ฅ2, ๐ฅ3])) ๐ ๐ฅ0 (๐ ๐ฅ1 (๐ ๐ฅ2 (๐๐๐๐๐ ๐ ๐ [๐ฅ3]))) ๐ ๐ฅ0 (๐ ๐ฅ1 (๐ ๐ฅ2 (๐ ๐ฅ3 (๐๐๐๐๐ ๐ ๐ [ ])))) ๐ ๐ฅ0 (๐ ๐ฅ1 (๐ ๐ฅ2 (๐ ๐ฅ3 ๐))) ๐๐๐๐๐ = ๐; ๐๐๐๐๐ ๐ฅ โงบ ๐ = ๐(๐ฅ, ๐๐๐๐๐(๐ )) ๐๐๐๐๐([๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3]) ๐(๐ฅ0, ๐๐๐๐๐([๐ฅ1, ๐ฅ2, ๐ฅ3])) ๐(๐ฅ0, ๐(๐ฅ1, ๐๐๐๐๐([๐ฅ2, ๐ฅ3]))) ๐(๐ฅ0, ๐(๐ฅ1, ๐(๐ฅ2, ๐๐๐๐๐([๐ฅ3])))) ๐(๐ฅ0, ๐(๐ฅ1, ๐(๐ฅ2, ๐(๐ฅ3, ๐๐๐๐๐([]))))) ๐(๐ฅ0, ๐(๐ฅ1, ๐(๐ฅ2, ๐(๐ฅ3, ๐)))) โถ / ๐ฅ0 โถ / ๐ฅ1 โถ / ๐ฅ2 โถ / ๐ฅ3 ๐ฅ0: (๐ฅ1: ๐ฅ2: ๐ฅ3: ) ๐ฅ๐ = [๐ฅ0, ๐ฅ1, ๐ฅ2, ๐ฅ3] And now letโs compare the results for both definitions of ๐๐๐๐๐. Again, the results are the same.
- 13. The way ๐๐๐๐๐ applies ๐ to ๐ฅ, ๐ฆ, z is by associating to the right. The way ๐๐๐๐๐ does it is by associating to the right. Thinking of ๐ as an infix operator can help to grasp this. ๐๐๐๐๐ ๐(๐ฅ, ๐(๐ฆ, ๐ง)) ๐ฅ โ ๐ฆ โ ๐ง ๐๐๐๐๐ ๐ ๐ ๐ฅ, ๐ฆ , ๐ง (๐ฅ โ ๐ฆ) โ ๐ง ๐๐๐๐๐ โ ๐ ๐ฅ, ๐ฆ, z = ๐ฅ โ (๐ฆ โ ๐ง โ ๐ ) ๐๐๐๐๐ (+) 0 [1,2,3] = 1 + (2 + (3 + 0)) = 6 ๐๐๐๐๐ (+) 0 [1,2,3] = ((0 + 1) + 2) + 3 = 6 ๐๐๐๐๐ โ 0 1,2,3 = 1 โ (2 โ (3 โ 0)) = 2 ๐๐๐๐๐ โ 0 1,2,3 = ((0 โ 1) โ 2) โ 3 = โ6 โ / ๐ฅ โ / ๐ฆ โ / ๐ง ๐ โ / โ ๐ง / โ ๐ฆ / ๐ ๐ฅ ๐๐๐๐๐ โ ๐ ๐ฅ, ๐ฆ, ๐ง = ((๐ โ ๐ฅ) โ ๐ฆ) โ ๐ง Addition is associative, so associating to the left and to the right yields the same result. But subtraction isnโt associative, so associating to the left yields a result that is different to the one yielded when associating to the right.
- 14. We have seen that Sergeiโs definitions of left and right folds make perfect sense. Not only are they simple and succinct, they are also free of implementation details like tail recursion. Thatโs all. I hope you found this slide deck useful. By the way, in case you are interested, see below for a whole series of slide decks dedicated to folding.