on master help sign in
clarify foldr and foldl docs
0258d77Harendra Kumar
src/Streamly/Prelude.hs+72−72
@@ -923,60 +923,69 @@ foldrM = P.foldrM
923
923
924-- | Lazy right associative fold.
924-- | Lazy right associative fold.
925--
925--
926−-- @foldr f z xs@ deconstructs @xs@ one element @x@ at a time, applying the
926+-- For lists a @foldr@ looks like:
927−-- function @f@ to @x@ and the tail of the output. The tail recurses until the
928−-- input finishes and @z@ is used as the tail end.
929--
927--
930-- @
928-- @
931−-- foldr f z xs == x1 \`f` tail1
929+-- foldr f z [] = z
932−-- tail1 == x2 \`f` tail2
930+-- foldr f z (x:xs) = x \`f` foldr f z xs
933−-- tail2 == x3 \`f` tail3
934−-- ...
935−-- tailn == xn \`f` z
936-- @
931-- @
937--
932--
933+-- The recursive expression is the second argument of the fold step `f`.
934+-- Therefore, the evaluation of the recursive call depends on `f`. It can
935+-- terminate recursion by not inspecting the second argument based on a
936+-- condition. When expanded fully, it results in the following right associated
938−-- Resulting in a right associated expression:
937+-- expression:
939--
938--
940-- @
939-- @
941-- foldr f z xs == x1 \`f` (x2 \`f` ...(xn \`f` z))
940-- foldr f z xs == x1 \`f` (x2 \`f` ...(xn \`f` z))
942-- @
941-- @
943--
942--
944−-- When the outermost operation in the fold function is a constructor, foldr
943+-- When `f` is a constructor, we can see that the first deconstruction of this
945−-- generates a structure that can be consumed lazily. For example:
944+-- expression would be @x1@ on the left and the recursive expression on the
945+-- right. Therefore, we can deconstruct it to access the input elements in the
946+-- first-in-first-out (FIFO) order and consume the reconstructed structure
947+-- lazily. The recursive expression on the right gets evaluated incrementall
948+-- as demanded by the consumer. For example:
946--
949--
947-- @
950-- @
948-- > S.foldr (:) [] $ S.fromList [1,2,3,4]
951-- > S.foldr (:) [] $ S.fromList [1,2,3,4]
949-- [1,2,3,4]
952-- [1,2,3,4]
950-- @
953-- @
951--
954--
955+-- When `f` is a function strict in its second argument, the right side of the
956+-- expression gets evaluated as follows:
957+--
952-- @
958-- @
953−-- 1 : tail
959+-- foldr f z xs == x1 \`f` tail1
954−-- 1 : (2 : tail)
960+-- tail1 == x2 \`f` tail2
955−-- 1 : (2 : (3 : tail))
961+-- tail2 == x3 \`f` tail3
956−-- 1 : (2 : (3 : (4 : [])))
962+-- ...
963+-- tailn == xn \`f` z
957-- @
964-- @
958--
965--
959−-- When the outermost operation is a function @foldr@ results in a right
966+-- In @foldl'@ we have both the arguments of `f` available at each step,
960−-- associated expression which cannot be reduced until the whole expression has
967+-- therefore, each step can be reduced immediately. However, in @foldr@ the
961−-- been built. Therefore, it ends up consuming the whole input, buffering the
968+-- second argument to `f` is a recursive call, therefore, it ends up building
962−-- whole expression in memory before reduction can start. For example:
969+-- the whole expression in memory before it can be reduced, consuming the whole
970+-- input. This makes @foldr@ much less efficient for reduction compared to
971+-- @foldl'@. For example:
963--
972--
964-- @
973-- @
965-- > S.foldr (+) 0 $ S.fromList [1,2,3,4]
974-- > S.foldr (+) 0 $ S.fromList [1,2,3,4]
966-- 10
975-- 10
967-- @
976-- @
968--
977--
969−-- @
978+-- When the underlying monad @m@ is strict (e.g. IO), then @foldr@ ends up
970−-- 1 + tail
979+-- evaluating all of its input because of strict evaluation of the recursive
971−-- 1 + (2 + tail)
980+-- call:
972−-- 1 + (2 + (3 + tail))
981+--
973−-- 1 + (2 + (3 + (4 + 0)))
982+-- >> S.foldr (\_ _ -> []) [] $ S.fromList (1:undefined)
974−-- @
983+-- >*** Exception: Prelude.undefined
975--
984--
976−-- In @foldr@, the output tail is the source of recursion, we can stop
985+-- In a lazy monad, we can consume the input lazily, and terminate the fold
977−-- recursion and terminate the fold by yielding a terminal value as tail:
986+-- by conditionally not inspecting the recursive expression.
978--
987--
979−-- >> S.foldr (\x rest -> if x == 3 then [] else x : rest) [] $ S.fromList [4,1,3,undefined]
988+-- >> runIdentity $ S.foldr (\x rest -> if x == 3 then [] else x : rest) [] $ S.fromList (4:1:3:undefined)
980-- >[4,1]
989-- >[4,1]
981--
990--
982-- The arguments to the folding function (@a -> b -> b@) are in the head and
991-- The arguments to the folding function (@a -> b -> b@) are in the head and
@@ -1014,37 +1023,38 @@ foldl = foldx
1014
1023
1015-- | Strict left associative fold.
1024-- | Strict left associative fold.
1016--
1025--
1017−-- @foldl' f z xs@ deconstructs @xs@ one element @x@ at a time, applying the
1026+-- For lists a @foldl@ looks like:
1018−-- function @f@ to the output accumulated till now (the head of the expression)
1019−-- and @x@. The accumulator starts with the initial value @z@ and keeps
1020−-- accumulating elements until the input finishes.
1021--
1027--
1022-- @
1028-- @
1023−-- head1 == z \`f` x1
1029+-- foldl f z [] = z
1024−-- head2 == head1 \`f` x2
1030+-- foldl f z (x:xs) = foldl f (z \`f` x) xs
1025−-- head3 == head2 \`f` x3
1026−-- ...
1027−-- foldl' f z xs == head(n-1) \`f` xn
1028-- @
1031-- @
1029--
1032--
1033+-- The recursive call at the head of the output expression is bound to be
1034+-- evaluated until recursion terminates,
1035+-- /deconstructing the whole input container/ and building the following left
1030−-- Recursively building a left associated expression:
1036+-- associated expression:
1031--
1037--
1032-- @
1038-- @
1033−-- foldl' f z xs == (((z \`f` x1) \`f` x2) ...) \`f` xn
1039+-- foldl f z xs == (((z \`f` x1) \`f` x2) ...) \`f` xn
1034-- @
1040-- @
1035--
1041--
1036−-- When the outermost operation in the fold function is a (left associated)
1042+-- When `f` is a constructor, we can see that the first deconstruction of this
1037−-- constructor, foldl' consumes the whole input to construct the new structure
1043+-- expression would be the recursive expression on the left and `xn` on the
1044+-- right. Therefore, it can access the input elements only in the reverse
1038−-- buffered in memory in the reverse order of the input. For example:
1045+-- (LIFO) order. For example:
1039--
1046--
1040-- @
1047-- @
1041-- > S.foldl' (flip (:)) [] $ S.fromList [1,2,3,4]
1048-- > S.foldl' (flip (:)) [] $ S.fromList [1,2,3,4]
1042-- [4,3,2,1]
1049-- [4,3,2,1]
1043-- @
1050-- @
1044--
1051--
1045−-- When the outermost operation is a function, @foldl'@ results in a left
1052+-- The strict left fold @foldl'@ forces the reduction of its argument @z \`f`
1046−-- associated expression which is incrementally reduced at each step of the
1047−-- fold, thus never building the whole expression in memory. For example:
1053+-- x@ before using it, therefore it never builds the whole expression in
1054+-- memory. Thus, @z \`f` x1@ would get reduced to @z1@ and then @z1 \`f` x2@
1055+-- would get reduced to @z2@ and so on, incrementally reducing the expression
1056+-- as it recurses. However, it evaluates the accumulator only to WHNF, it may
1057+-- further help to use a strict data structure as accumulator. For example:
1048--
1058--
1049-- @
1059-- @
1050-- > S.foldl' (+) 0 $ S.fromList [1,2,3,4]
1060-- > S.foldl' (+) 0 $ S.fromList [1,2,3,4]
@@ -1057,42 +1067,32 @@ foldl = foldx
1057-- ((0 + 1) + 2) + 3
1067-- ((0 + 1) + 2) + 3
1058-- (((0 + 1) + 2) + 3) + 4
1068-- (((0 + 1) + 2) + 3) + 4
1059-- @
1069-- @
1060−-- @
1061−-- 0 + 1 => reduce to 1
1062−-- 1 + 2 => reduce to 3
1063−-- 3 + 3 => reduce to 6
1064−-- 6 + 4 => reduce to 10
1065−-- @
1066--
1070--
1067−-- We can stop recursion and terminate the fold by yielding an expression that
1071+-- @foldl@ strictly deconstructs the whole input container irrespective of
1068−-- is independent of the input element:
1072+-- whether it needs it or not:
1069--
1073--
1070−-- >> S.foldl' (\acc x -> if acc >= 8 then acc else x + acc) 0 $ S.fromList [4,1,3,undefined]
1074+-- >> S.foldl' (\acc x -> if x == 3 then acc else x : acc) [] $ S.fromList (4:1:3:undefined)
1071−-- >8
1075+-- >*** Exception: Prelude.undefined
1072--
1076--
1073−-- To stop on encountering the number 3 we will have to store the previous
1077+-- However, evaluation of the items contained in the input container is lazy as
1074−-- number in the accumulator so that we do not depend on the current input:
1078+-- demanded by the fold step function:
1075--
1079--
1076−-- @
1077−-- > S.foldl' (\(acc, prev) x -> if prev == 3 then (acc,prev) else (x + acc,x))
1080+-- >> S.foldl' (\acc x -> if x == 3 then acc else x : acc) [] $ S.fromList [4,1,3,undefined]
1078−-- (0,0) $ S.fromList \[4,1,3,undefined]
1081+-- >[4,1]
1079−-- (8,3)
1080−-- @
1081--
1082--
1083+-- To perform a left fold without consuming all the input one can use @scanl@
1084+-- to stream the intermediate results of the fold and use them lazily.
1085+--
1082−-- To map the fold operation to stateful or event-driven programming, we can
1086+-- In stateful or event-driven programming, we can consider @z@ as the initial
1083−-- consider @z@ as the initial state and the stream being folded as a stream of
1087+-- state and the stream being folded as a stream of events, thus @foldl'@
1084−-- events, thus @foldl'@ processes all the events in the stream updating the
1088+-- processes all the events in the stream updating the state on each event and
1085−-- state on each event and then ultimately returning the final state.
1089+-- then ultimately returning the final state.
1086--
1090--
1087-- The arguments to the folding function (@b -> a -> b@) are in the head and
1091-- The arguments to the folding function (@b -> a -> b@) are in the head and
1088-- tail order of the output expression, @b@ is the head and @a@ is the tail.
1092-- tail order of the output expression, @b@ is the head and @a@ is the tail.
1089-- Remember, in a left fold the zero is on the left, at the head of the
1093-- Remember, in a left fold the zero is on the left, at the head of the
1090-- expression.
1094-- expression.
1091--
1095--
1092−-- IMPORTANT: 'foldl'' evaluates the accumulator to WHNF. To avoid building
1093−-- lazy expressions inside the accumulator, and to help GHC optimize better, it
1094−-- is recommended that a strict data structure is used for accumulator.
1095−--
1096-- @since 0.2.0
1096-- @since 0.2.0
1097{-# INLINE foldl' #-}
1097{-# INLINE foldl' #-}
1098foldl' :: Monad m => (b -> a -> b) -> b -> SerialT m a -> m b
1098foldl' :: Monad m => (b -> a -> b) -> b -> SerialT m a -> m b
@@ -1519,8 +1519,8 @@ scanlM' step begin m = fromStreamD $ D.scanlM' step begin $ toStreamD m
1519-- @
1519-- @
1520--
1520--
1521-- IMPORTANT: 'scanl'' evaluates the accumulator to WHNF. To avoid building
1521-- IMPORTANT: 'scanl'' evaluates the accumulator to WHNF. To avoid building
1522−-- lazy expressions inside the accumulator, and to help GHC optimize better, it
1522+-- lazy expressions inside the accumulator, it is recommended that a strict
1523−-- is recommended that a strict data structure is used for accumulator.
1523+-- data structure is used for accumulator.
1524--
1524--
1525-- @since 0.2.0
1525-- @since 0.2.0
1526{-# INLINE scanl' #-}
1526{-# INLINE scanl' #-}