Skip to content

Commit 3649ea9

Browse files
LysxiaBodigrim
authored andcommitted
Add foldr' and document the laziness of foldr
1 parent 6a45060 commit 3649ea9

File tree

3 files changed

+57
-0
lines changed

3 files changed

+57
-0
lines changed

src/Data/Text.hs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ module Data.Text
9898
, foldl1
9999
, foldl1'
100100
, foldr
101+
, foldr'
101102
, foldr1
102103

103104
-- ** Special folds
@@ -992,6 +993,22 @@ foldl1' f t = S.foldl1' f (stream t)
992993
-- | /O(n)/ 'foldr', applied to a binary operator, a starting value
993994
-- (typically the right-identity of the operator), and a 'Text',
994995
-- reduces the 'Text' using the binary operator, from right to left.
996+
--
997+
-- If the binary operator is strict in its second argument, use 'foldr''
998+
-- instead.
999+
--
1000+
-- 'foldr' is lazy like 'Data.List.foldr' for lists: evaluation actually
1001+
-- traverses the 'Text' from left to right, only as far as it needs to.
1002+
--
1003+
-- For example, 'head' can be defined with /O(1)/ complexity using 'foldr':
1004+
--
1005+
-- @
1006+
-- head :: Text -> Char
1007+
-- head = foldr const (error "head empty")
1008+
-- @
1009+
--
1010+
-- Searches from left to right with short-circuiting behavior can
1011+
-- also be defined using 'foldr' (/e.g./, 'any', 'all', 'find', 'elem').
9951012
foldr :: (Char -> a -> a) -> a -> Text -> a
9961013
foldr f z t = S.foldr f z (stream t)
9971014
{-# INLINE foldr #-}
@@ -1002,6 +1019,13 @@ foldr1 :: HasCallStack => (Char -> Char -> Char) -> Text -> Char
10021019
foldr1 f t = S.foldr1 f (stream t)
10031020
{-# INLINE foldr1 #-}
10041021

1022+
-- | /O(n)/ A strict version of 'foldr'.
1023+
--
1024+
-- 'foldr'' evaluates as a right-to-left traversal using constant stack space.
1025+
foldr' :: (Char -> a -> a) -> a -> Text -> a
1026+
foldr' f z t = S.foldl' (P.flip f) z (reverseStream t)
1027+
{-# INLINE foldr' #-}
1028+
10051029
-- -----------------------------------------------------------------------------
10061030
-- ** Special folds
10071031

src/Data/Text/Lazy.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,16 @@ foldl1' f t = S.foldl1' f (stream t)
784784
-- | /O(n)/ 'foldr', applied to a binary operator, a starting value
785785
-- (typically the right-identity of the operator), and a 'Text',
786786
-- reduces the 'Text' using the binary operator, from right to left.
787+
--
788+
-- 'foldr' is lazy like 'Data.List.foldr' for lists: evaluation actually
789+
-- traverses the 'Text' from left to right, only as far as it needs to.
790+
--
791+
-- For example, 'head' can be defined with /O(1)/ complexity using 'foldr':
792+
--
793+
-- @
794+
-- head :: Text -> Char
795+
-- head = foldr const (error "head empty")
796+
-- @
787797
foldr :: (Char -> a -> a) -> a -> Text -> a
788798
foldr f z t = S.foldr f z (stream t)
789799
{-# INLINE foldr #-}

tests/Tests/Properties/Folds.hs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ module Tests.Properties.Folds
88
) where
99

1010
import Control.Arrow (second)
11+
import Control.Exception (ErrorCall, evaluate, try)
1112
import Data.Word (Word8, Word16)
1213
import Test.Tasty (TestTree, testGroup)
14+
import Test.Tasty.HUnit (testCase, assertFailure)
1315
import Test.Tasty.QuickCheck (testProperty, Small(..), (===), applyFun, applyFun2)
1416
import Tests.QuickCheckUtils
1517
import qualified Data.List as L
@@ -47,13 +49,32 @@ sf_foldr (applyFun -> p) (applyFun2 -> f) z =
4749
where _types = f :: Char -> Char -> Char
4850
t_foldr (applyFun2 -> f) z = L.foldr f z `eqP` T.foldr f z
4951
where _types = f :: Char -> Char -> Char
52+
t_foldr' (applyFun2 -> f) z = L.foldr f z `eqP` T.foldr' f z
53+
where _types = f :: Char -> Char -> Char
5054
tl_foldr (applyFun2 -> f) z = L.foldr f z `eqPSqrt` TL.foldr f z
5155
where _types = f :: Char -> Char -> Char
5256
sf_foldr1 (applyFun -> p) (applyFun2 -> f) =
5357
(L.foldr1 f . L.filter p) `eqPSqrt` (S.foldr1 f . S.filter p)
5458
t_foldr1 (applyFun2 -> f) = L.foldr1 f `eqP` T.foldr1 f
5559
tl_foldr1 (applyFun2 -> f) = L.foldr1 f `eqPSqrt` TL.foldr1 f
5660

61+
-- Distinguish foldl/foldr from foldl'/foldr'
62+
63+
fold_apart :: IO ()
64+
fold_apart = do
65+
ok (T.foldr f () (T.pack "az"))
66+
ko (T.foldr' f () (T.pack "az"))
67+
ok (T.foldl (flip f) () (T.pack "za"))
68+
ko (T.foldl' (flip f) () (T.pack "za"))
69+
where
70+
f c _ = if c == 'z' then error "catchme" else ()
71+
ok = evaluate
72+
ko t = do
73+
x <- try (evaluate t)
74+
case x :: Either ErrorCall () of
75+
Left _ -> pure ()
76+
Right _ -> assertFailure "test should have failed but didn't"
77+
5778
-- Special folds
5879

5980
s_concat_s = (L.concat . unSqrt) `eq` (unpackS . S.unstream . S.concat . map packS . unSqrt)
@@ -187,10 +208,12 @@ testFolds =
187208
testProperty "tl_foldl1'" tl_foldl1',
188209
testProperty "sf_foldr" sf_foldr,
189210
testProperty "t_foldr" t_foldr,
211+
testProperty "t_foldr'" t_foldr',
190212
testProperty "tl_foldr" tl_foldr,
191213
testProperty "sf_foldr1" sf_foldr1,
192214
testProperty "t_foldr1" t_foldr1,
193215
testProperty "tl_foldr1" tl_foldr1,
216+
testCase "fold_apart" fold_apart,
194217

195218
testGroup "special" [
196219
testProperty "s_concat_s" s_concat_s,

0 commit comments

Comments
 (0)