====== Folds ======
* inspired by [[https://wiki.haskell.org/Fold|Haskell wiki - Fold]]
* fold functions are used to fold lists
* folding means combining list elements
* fold functions take at least the following parameters:
* a function that combines two elements of the list
* the list of elements to be combined
* all examples
* compile
* warning free with
* compiler: GHC 8.10.4 using -Wall
* example:
*
main :: IO ()
main = print (foldr (+) 0 [3::Integer, 5, 7])
* in Prelude, there is two fold functions available
* ''foldr''
* ''foldl''
* both have advantages and disadvantages in different cases
^ foldr ^ foldl ^
| right fold | left fold |
| can deal with __infinite lists__ | can only deal with __finite lists__ |
| (lazy) | (strict) |
| needs more __heap__ space | needs more __stack__ space |
| | can lead to __stack overflows__ |
| | efficiently compiled as a loop |
| __slower__ | __fast__ |
| __initial value__ is used __when__ one reaches the __end of the list__ | __initial value__ combined __with__ the __first element__ of the list |
| __immediately returns__ if f does not demand the rest of the recursion | __immediately calls itself__ with new parameters until it reaches the end of the list |
| order is kept | order is reverse |
| type: ''Foldable t => __(a -> b -> b)__ -> b -> t a -> b'' | type: ''Foldable t => __(b -> a -> b)__ -> b -> t a -> b'' |
| Note the order in underlined function. ||
* there is further variants
* ''foldl' :: Foldable t => (b -> a -> b) -> b -> t a -> b''
* ''foldl1 :: (a -> a -> a) -> [a] -> a''
* ''foldl1' :: (a -> a -> a) -> [a] -> a ''
* ''foldr1 :: (a -> a -> a) -> [a] -> a''
^ variants with single quote ("'") as postfix ^ variants with one ("1") as postfix ^
| ''foldl' '', ''foldl1' '' | ''foldl1'', ''foldl1' '', ''foldr1'' |
| stricter variant | no initial value as parameter |
| forces the evaluation of the initial parameter before making the recursive call | initial value is last and first element of the list, respectively |
===== foldr =====
* right fold
* (lazy)
* __initial value__ is used __when__ one reaches the __end of the list__
* definition:
-- if the list is empty, the result is the initial value z; else
-- apply f to the first element and the result of folding the rest
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
* __immediately returns__ if f does not demand the rest of the recursion
* can deal with __infinite lists__
* example:
main :: IO ()
main = print f1
f1 :: Integer
f1 = foldr (sumIfNot 100000000) 0 [0..100000000]
sumIfNot :: Integer -> Integer -> Integer -> Integer
sumIfNot nUntil nA nB
| nA == nUntil = nA
| otherwise = nA + nB
* executes with result:
5000000050000000
* executes in approximately __**15 seconds**__ on Intel(R) Core(TM) i5-8400 @ 2.80GHz
* executes with allocating approximately 8 GByte heap sapce:
* {{:codesnippets:heapconsumptionfoldr.png?400|Heap consumption}}
* example, list here is infinite:
main :: IO ()
main = print f1
f1 :: Integer
f1 = foldr (sumIfNot 100000000) 0 [0..]
sumIfNot :: Integer -> Integer -> Integer -> Integer
sumIfNot nUntil nA nB
| nA == nUntil = nA
| otherwise = nA + nB
* executes with result:
5000000050000000
* executes in approximately __**15 seconds**__ on Intel(R) Core(TM) i5-8400 @ 2.80GHz
* using simmilar heapsapce as above
===== foldl =====
* left fold
* (fast)
* __initial value__ combined __with__ the __first element__ of the list
* recursively combining the results of combining all but the last element with the last one
* definition:
-- if the list is empty, the result is the initial value; else
-- we recurse immediately, making the new initial value the result
-- of combining the old initial value with the first element.
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
* __immediately calls itself__ with new parameters until it reaches the end of the list
* can only deal with __finite lists__
* efficiently compiled as a loop
* can lead to __stack overflows__
* example:
main :: IO ()
main = print f1
f1 :: Integer
f1 = foldl (sumIfNot 100000000) 0 [0..100000000]
sumIfNot :: Integer -> Integer -> Integer -> Integer
sumIfNot nUntil nA nB
| nA == nUntil = nA
| otherwise = nA + nB
* executes with result:
5000000050000000
* executes within approximately __**2 seconds**__ on Intel(R) Core(TM) i5-8400 @ 2.80GHz
* executes almost without allocating heap space:
* {{:codesnippets:heapconsumptionfoldl.png?100|Heap space when using foldl to sum large list of Integers}}
* example, using infinite list:
main :: IO ()
main = print f1
f1 :: Integer
f1 = foldl (sumIfNot 100000000) 0 [0..]
sumIfNot :: Integer -> Integer -> Integer -> Integer
sumIfNot nUntil nA nB
| nA == nUntil = nA
| otherwise = nA + nB
* will not stop executing, unless memory error stops execution
* executes allocating heap space, endlessly:
* {{:codesnippets:heapconsumptionfoldloninfitelist.png?400|Heap space when using foldl to sum infinite list of Integers}}
* example, using ''sumIfNot __100__'':
main :: IO ()
main = print f1
f1 :: Integer
f1 = foldl (sumIfNot 100) 0 [0..100000000]
sumIfNot :: Integer -> Integer -> Integer -> Integer
sumIfNot nUntil nA nB
| nA == nUntil = nA
| otherwise = nA + nB
* executes with result:
5000000050000000
* NOTE: The function sumIfNot does not stop the recursion!
===== foldl' =====
* left fold
* same as ''foldl'', but forces the evaluation of the initial parameter before making the recursive call (stricter variant)
* can be imported from library ''Data.List''
* implementation:
foldl' :: (b -> a -> b) -> b -> t a -> b
foldl' f z0 xs = foldr f' id xs z0
where f' x k z = k $! f z x
* example:
import qualified Data.List as L
main :: IO ()
main = print f1
f1 :: Integer
f1 = L.foldl' (sumIfNot 100000000) 0 [0..100000000]
sumIfNot :: Integer -> Integer -> Integer -> Integer
sumIfNot nUntil nA nB
| nA == nUntil = nA
| otherwise = nA + nB
* executes with result:
5000000050000000
* executes within approximately __**3 seconds**__ on Intel(R) Core(TM) i5-8400 @ 2.80GHz
* executes with small allocation of heap space
* example, using infinite list:
import qualified Data.List as L
main :: IO ()
main = print f1
f1 :: Integer
f1 = L.foldl' (sumIfNot 100000000) 0 [0..]
sumIfNot :: Integer -> Integer -> Integer -> Integer
sumIfNot nUntil nA nB
| nA == nUntil = nA
| otherwise = nA + nB
* will not stop executing
* executes with small allocation of heap space
* example, using ''sumIfNot __100__'':
import qualified Data.List as L
main :: IO ()
main = print f1
f1 :: Integer
f1 = L.foldl' (sumIfNot 100) 0 [0..100000000]
sumIfNot :: Integer -> Integer -> Integer -> Integer
sumIfNot nUntil nA nB
| nA == nUntil = nA
| otherwise = nA + nB
* executes with result:
5000000050000000
* NOTE: The function sumIfNot does not stop the recursion!
* executes within approximately __**3 seconds**__ on Intel(R) Core(TM) i5-8400 @ 2.80GHz
* executes with small allocation of heap space
====== Order of execution ======
The implications regarding order of execution:
* example, using foldr:
main :: IO ()
main =
do
print s
print $ f1 s
s = ['a'..'z']
f1 :: String -> String
f1 s = foldr (:) [] s
* executes with following output:
"abcdefghijklmnopqrstuvwxyz"
"abcdefghijklmnopqrstuvwxyz"
* example, using foldl:
main :: IO ()
main =
do
print s
print $ f1 s
s = ['a'..'z']
f1 :: String -> String
f1 s = foldl (flip (:)) [] s
* executes with following output:
"abcdefghijklmnopqrstuvwxyz"
"zyxwvutsrqponmlkjihgfedcba"
* NOTE: Parameter order of function (:) needed to be flipped to ''[a] -> b -> [a]'' from ''b -> [a] -> [a]'', and would not compile because of type mismatch.
* example, using foldl':
import qualified Data.List as L
main :: IO ()
main =
do
print s
print $ f1 s
s = ['a'..'z']
f1 :: String -> String
f1 s = L.foldl' (flip (:)) [] s
* executes with following output:
"abcdefghijklmnopqrstuvwxyz"
"zyxwvutsrqponmlkjihgfedcba"
* NOTE: Parameter order of function (:) needed to be flipped as well as for foldl.
* example, with __not__ commutative operation (''-''):
import qualified Data.List as L
main :: IO ()
main =
do
print lnNumbers1
print lnNumbers2
print $ f1 lnNumbers1
print $ f1 lnNumbers2
print $ f2 lnNumbers1
print $ f2 lnNumbers2
print $ f3 lnNumbers1
print $ f3 lnNumbers2
lnNumbers1 = [31,7]
lnNumbers2 = [31,7,3]
f1 :: [Integer] -> Integer
f1 ln = foldr (-) 0 ln
f2 :: [Integer] -> Integer
f2 ln = foldl (flip (-)) 0 ln
f3 :: [Integer] -> Integer
f3 ln = L.foldl' (flip (-)) 0 ln
* executes with following output:
[31,7]
[31,7,3]
24
27
-24
27
-24
27
====== Keeping order and beeing fast? ======
* use case
* finite lists
* large lists
* order of operations to be kept as is foldr
* example, using foldr:
main :: IO ()
main =
do
print $ f1 lnNumbers1
print $ f1 lnNumbers2
print $ f1 lnNumbers3
lnNumbers1 = [31,13]
lnNumbers2 = [31,13,3]
lnNumbers3 = [1..100000000]
f1 :: [Integer] -> Integer
f1 ln = foldr (-) 0 ln
* executes, with output:
18
21
-50000000
* executes within approximately __**15 seconds**__ on Intel(R) Core(TM) i5-8400 @ 2.80GHz
* executes using large heap space
* {{:codesnippets:heapconsumptionfoldronnoncommutativeop.png?200|}}
* example, using ''foldl'' and ''reverse'':
import qualified Data.List as L
main :: IO ()
main =
do
print $ f2 lnNumbers1
print $ f2 lnNumbers2
print $ f2 lnNumbers3
lnNumbers1 = [31,13]
lnNumbers2 = [31,13,3]
lnNumbers3 = [1..100000000]
f2 :: [Integer] -> Integer
f2 ln = foldlFlpRvrs (-) 0 ln
foldlFlpRvrs :: (a -> b -> b) -> b -> [a] -> b
foldlFlpRvrs f initial l = L.foldl' (flip f) initial (reverse l)
* executes, with output:
18
21
-50000000
* executes within approximately __**15 seconds**__ on Intel(R) Core(TM) i5-8400 @ 2.80GHz
* executes using large heap space
* {{:codesnippets:heapconsumptionfoldlfronnoncommutativeop.png?200|}}
* regardless of whether ''L.foldl' '' or ''foldl'' is used
* regardless of whether alternative ''reverse'' functions are implemented
* e.g.:
reverse' :: [a] -> [a]
reverse' (x:rlx) = reverse'' [x] rlx
where
reverse'' :: [a] -> [a] -> [a]
reverse'' lxReverse [] = lxReverse
reverse'' lxReverse (x:rlx) = reverse'' (x : lxReverse) rlx
* or
reverse' :: [a] -> [a]
reverse' l = foldl (flip (:)) [] l
* or
reverse' :: [a] -> [a]
reverse' l = L.foldl' (flip (:)) [] l
====== Conclusion and selection ======
* if the order of computation can be reverse, and if for finite lists -> use ''foldl'', but....
* if the initial value is the first and the last respectively, and there is at least one element in the list use ''foldl1 ''
* if the initial value needs to be evaluated first -> use ''foldl' '', but...
* if the initial value also is the first and the last respectively use ''foldl1' ''
* if the order should be kept or if for infinite lists -> use ''foldr'', but...
* if the initial value is the first and the last respectively use ''foldr1''
===== ✎ =====
~~DISCUSSION~~