Skip to content

Commit b5902d3

Browse files
authored
Merge pull request #92 from sammthomson/lazierlazylist
Fixing several stack-unsafe functions in Data.List.Lazy
2 parents abc5578 + 4ec5dc8 commit b5902d3

File tree

3 files changed

+58
-35
lines changed

3 files changed

+58
-35
lines changed

src/Data/List/Lazy.purs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,7 @@ null = isNothing <<< uncons
186186
-- |
187187
-- | Running time: `O(n)`
188188
length :: forall a. List a -> Int
189-
length xs = go (step xs)
190-
where
191-
go Nil = 0
192-
go (Cons _ xs') = 1 + go (step xs')
189+
length = foldl (\l _ -> l + 1) 0
193190

194191
--------------------------------------------------------------------------------
195192
-- Extending arrays ------------------------------------------------------------
@@ -386,10 +383,7 @@ alterAt n f xs = List (go n <$> unwrap xs)
386383
-- |
387384
-- | Running time: `O(n)`
388385
reverse :: forall a. List a -> List a
389-
reverse = go nil <<< step
390-
where
391-
go acc Nil = acc
392-
go acc (Cons x xs) = go (cons x acc) (step xs)
386+
reverse xs = Z.defer \_ -> foldl (flip cons) nil xs
393387

394388
-- | Flatten a list of lists.
395389
-- |

src/Data/List/Lazy/Types.purs

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -98,43 +98,35 @@ instance functorList :: Functor List where
9898
go (Cons x xs') = Cons (f x) (f <$> xs')
9999

100100
instance foldableList :: Foldable List where
101-
foldr o b xs = go (step xs)
102-
where
103-
go Nil = b
104-
go (Cons a as) = a `o` foldr o b as
101+
-- calls foldl on the reversed list
102+
foldr op z xs = foldl (flip op) z (rev xs) where
103+
rev = foldl (flip cons) nil
105104

106-
foldl o b xs = go (step xs)
107-
where
108-
go Nil = b
109-
go (Cons a as) = foldl o (b `o` a) as
105+
foldl = go where
106+
-- `go` is needed to ensure the function is tail-call optimized
107+
go op b xs = case step xs of
108+
Nil -> b
109+
Cons hd tl -> go op (b `op` hd) tl
110110

111-
foldMap f xs = go (step xs)
112-
where
113-
go Nil = mempty
114-
go (Cons x xs') = f x <> foldMap f xs'
111+
foldMap f = foldl (\b a -> b <> f a) mempty
115112

116113
instance unfoldableList :: Unfoldable List where
117-
unfoldr f b = go (f b)
118-
where
119-
go Nothing = nil
120-
go (Just (Tuple a b')) = a : Z.defer \_ -> go (f b')
114+
unfoldr = go where
115+
go f b = Z.defer \_ -> case f b of
116+
Nothing -> nil
117+
Just (Tuple a b') -> a : go f b'
121118

122119
instance traversableList :: Traversable List where
123-
traverse f xs = go (step xs)
124-
where
125-
go Nil = pure nil
126-
go (Cons x xs') = cons <$> f x <*> traverse f xs'
120+
traverse f =
121+
foldr (\a b -> cons <$> f a <*> b) (pure nil)
127122

128-
sequence xs = go (step xs)
129-
where
130-
go Nil = pure nil
131-
go (Cons x xs') = cons <$> x <*> sequence xs'
123+
sequence = traverse id
132124

133125
instance applyList :: Apply List where
134126
apply = ap
135127

136128
instance applicativeList :: Applicative List where
137-
pure = flip cons nil
129+
pure a = a : nil
138130

139131
instance bindList :: Bind List where
140132
bind xs f = List (go <$> unwrap xs)
@@ -170,7 +162,8 @@ instance extendList :: Extend List where
170162
newtype NonEmptyList a = NonEmptyList (Lazy (NonEmpty List a))
171163

172164
toList :: forall a. NonEmptyList a -> List a
173-
toList (NonEmptyList nel) = case force nel of x :| xs -> x : xs
165+
toList (NonEmptyList nel) = Z.defer \_ ->
166+
case force nel of x :| xs -> x : xs
174167

175168
derive instance newtypeNonEmptyList :: Newtype (NonEmptyList a) _
176169

test/Test/Data/List/Lazy.purs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import Control.Monad.Eff (Eff)
77
import Control.Monad.Eff.Console (CONSOLE, log)
88

99
import Data.Lazy as Z
10-
import Data.List.Lazy (List, nil, cons, singleton, transpose, take, iterate, filter, uncons, foldM, range, unzip, zip, length, zipWithA, replicate, repeat, zipWith, intersectBy, intersect, deleteBy, delete, unionBy, union, nubBy, nub, groupBy, group, span, dropWhile, drop, takeWhile, slice, catMaybes, mapMaybe, filterM, concat, concatMap, reverse, alterAt, modifyAt, updateAt, deleteAt, insertAt, findLastIndex, findIndex, elemLastIndex, elemIndex, init, tail, last, head, insertBy, insert, snoc, null, replicateM, fromFoldable, (:), (\\), (!!))
10+
import Data.List.Lazy (List, nil, cons, foldl, foldr, foldMap, singleton, transpose, take, iterate, filter, uncons, foldM, range, unzip, zip, length, zipWithA, replicate, repeat, zipWith, intersectBy, intersect, deleteBy, delete, unionBy, union, nubBy, nub, groupBy, group, span, dropWhile, drop, takeWhile, slice, catMaybes, mapMaybe, filterM, concat, concatMap, reverse, alterAt, modifyAt, updateAt, deleteAt, insertAt, findLastIndex, findIndex, elemLastIndex, elemIndex, init, tail, last, head, insertBy, insert, snoc, null, replicateM, fromFoldable, (:), (\\), (!!))
1111
import Data.List.Lazy.NonEmpty as NEL
1212
import Data.Maybe (Maybe(..), isNothing, fromJust)
13+
import Data.Monoid.Additive (Additive(..))
1314
import Data.NonEmpty ((:|))
15+
import Data.Traversable (traverse)
1416
import Data.Tuple (Tuple(..))
1517

1618
import Partial.Unsafe (unsafePartial)
@@ -22,6 +24,31 @@ testListLazy = do
2224
let
2325
l = fromFoldable
2426
nel xxs = NEL.NonEmptyList (Z.defer \_ -> xxs)
27+
longList = range 1 100000
28+
29+
log "append should be stack-safe"
30+
assert $ length (longList <> longList) == (2 * length longList)
31+
32+
log "map should be stack-safe"
33+
assert $ (last $ (_ + 1) <$> longList) == ((_ + 1) <$> last longList)
34+
35+
log "foldl should be stack-safe"
36+
void $ pure $ foldl (+) 0 longList
37+
38+
log "foldr should be stack-safe"
39+
void $ pure $ foldr (+) 0 longList
40+
41+
log "foldMap should be stack-safe"
42+
void $ pure $ foldMap Additive longList
43+
44+
log "foldMap should be left-to-right"
45+
assert $ foldMap show (range 1 5) == "12345"
46+
47+
log "traverse should be stack-safe"
48+
assert $ ((traverse Just longList) >>= last) == last longList
49+
50+
log "bind should be stack-safe"
51+
void $ pure $ last $ longList >>= pure
2552

2653
log "singleton should construct an list with a single value"
2754
assert $ singleton 1 == l [1]
@@ -57,6 +84,9 @@ testListLazy = do
5784
log "null should return true for an empty list"
5885
assert $ null nil' == true
5986

87+
log "length should be stack-safe"
88+
assert $ length longList == 100000
89+
6090
log "length should return the number of items in an list"
6191
assert $ length nil' == 0
6292
assert $ length (l [1]) == 1
@@ -66,6 +96,9 @@ testListLazy = do
6696
assert $ l [1, 2, 3] `snoc` 4 == l [1, 2, 3, 4]
6797
assert $ nil' `snoc` 1 == l [1]
6898

99+
log "insert should be stack-safe"
100+
assert $ last (insert 100001 longList) == Just 100001
101+
69102
log "insert should add an item at the appropriate place in a sorted list"
70103
assert $ insert 1.5 (l [1.0, 2.0, 3.0]) == l [1.0, 1.5, 2.0, 3.0]
71104
assert $ insert 4 (l [1, 2, 3]) == l [1, 2, 3, 4]
@@ -167,6 +200,9 @@ testListLazy = do
167200
assert $ (reverse (l [1, 2, 3])) == l [3, 2, 1]
168201
assert $ (reverse nil') == nil'
169202

203+
log "reverse should be stack-safe"
204+
assert $ head (reverse longList) == last longList
205+
170206
log "concat should join an list of lists"
171207
assert $ (concat (l [l [1, 2], l [3, 4]])) == l [1, 2, 3, 4]
172208
assert $ (concat (l [l [1], nil'])) == l [1]

0 commit comments

Comments
 (0)