Skip to content

Commit ab3189f

Browse files
matthewleonpaf31
authored andcommitted
indexed foldable-traversable instances (#134)
* indexed foldable-traversable instances * update foldable-traversable dep * TraversableWithIndex implementation * Data.List: reuse mapWithIndex from Data.FunctorWithIndex * fix List.foldrWithIndex in turn fixes List.mapWithIndex * simplify List right fold code reuse left fold for reversals * add failing mapWithIndex test for Lazy List * fix Lazy List foldrWithIndex * indexed instance tests * clarify reversal in traversableWithIndexList
1 parent 6c8aaad commit ab3189f

File tree

6 files changed

+124
-17
lines changed

6 files changed

+124
-17
lines changed

bower.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"purescript-tailrec": "^3.3.0",
2626
"purescript-unfoldable": "^3.0.0",
2727
"purescript-partial": "^1.0.0",
28-
"purescript-foldable-traversable": "^3.3.0"
28+
"purescript-foldable-traversable": "^3.4.0"
2929
},
3030
"devDependencies": {
3131
"purescript-arrays": "^4.0.0",

src/Data/List.purs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM, tailRecM2)
102102

103103
import Data.Bifunctor (bimap)
104104
import Data.Foldable (class Foldable, foldr, any, foldl)
105+
import Data.FunctorWithIndex (mapWithIndex) as FWI
105106
import Data.List.Types (List(..), (:))
106107
import Data.List.Types (NonEmptyList(..)) as NEL
107108
import Data.Maybe (Maybe(..))
@@ -426,11 +427,10 @@ catMaybes = mapMaybe id
426427

427428

428429
-- | Apply a function to each element and its index in a list starting at 0.
430+
-- |
431+
-- | Deprecated. Use Data.FunctorWithIndex instead.
429432
mapWithIndex :: forall a b. (Int -> a -> b) -> List a -> List b
430-
mapWithIndex f lst = reverse $ go 0 lst Nil
431-
where
432-
go _ Nil acc = acc
433-
go n (x : xs) acc = go (n+1) xs (f n x : acc)
433+
mapWithIndex = FWI.mapWithIndex
434434

435435
--------------------------------------------------------------------------------
436436
-- Sorting ---------------------------------------------------------------------

src/Data/List/Lazy/Types.purs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import Control.Lazy as Z
1010
import Control.MonadPlus (class MonadPlus)
1111
import Control.MonadZero (class MonadZero)
1212
import Control.Plus (class Plus)
13-
1413
import Data.Eq (class Eq1, eq1)
1514
import Data.Foldable (class Foldable, foldMap, foldl, foldr)
15+
import Data.FoldableWithIndex (class FoldableWithIndex, foldlWithIndex, foldrWithIndex)
16+
import Data.FunctorWithIndex (class FunctorWithIndex)
1617
import Data.Lazy (Lazy, defer, force)
1718
import Data.Maybe (Maybe(..))
1819
import Data.Monoid (class Monoid, mempty)
@@ -21,7 +22,8 @@ import Data.NonEmpty (NonEmpty, (:|))
2122
import Data.NonEmpty as NE
2223
import Data.Ord (class Ord1, compare1)
2324
import Data.Traversable (class Traversable, traverse, sequence)
24-
import Data.Tuple (Tuple(..))
25+
import Data.TraversableWithIndex (class TraversableWithIndex)
26+
import Data.Tuple (Tuple(..), snd)
2527
import Data.Unfoldable (class Unfoldable)
2628

2729
-- | A lazy linked list.
@@ -105,6 +107,9 @@ instance functorList :: Functor List where
105107
go Nil = Nil
106108
go (Cons x xs') = Cons (f x) (f <$> xs')
107109

110+
instance functorWithIndexList :: FunctorWithIndex Int List where
111+
mapWithIndex f = foldrWithIndex (\i x acc -> f i x : acc) nil
112+
108113
instance foldableList :: Foldable List where
109114
-- calls foldl on the reversed list
110115
foldr op z xs = foldl (flip op) z (rev xs) where
@@ -120,6 +125,22 @@ instance foldableList :: Foldable List where
120125

121126
foldMap f = foldl (\b a -> b <> f a) mempty
122127

128+
instance foldableWithIndexList :: FoldableWithIndex Int List where
129+
foldrWithIndex f b xs =
130+
-- as we climb the reversed list, we decrement the index
131+
snd $ foldl
132+
(\(Tuple i b') a -> Tuple (i - 1) (f (i - 1) a b'))
133+
(Tuple len b)
134+
revList
135+
where
136+
Tuple len revList = rev (Tuple 0 nil) xs
137+
where
138+
-- As we create our reversed list, we count elements.
139+
rev = foldl (\(Tuple i acc) a -> Tuple (i + 1) (a : acc))
140+
foldlWithIndex f acc =
141+
snd <<< foldl (\(Tuple i b) a -> Tuple (i + 1) (f i b a)) (Tuple 0 acc)
142+
foldMapWithIndex f = foldlWithIndex (\i acc -> append acc <<< f i) mempty
143+
123144
instance unfoldableList :: Unfoldable List where
124145
unfoldr = go where
125146
go f b = Z.defer \_ -> case f b of
@@ -132,6 +153,10 @@ instance traversableList :: Traversable List where
132153

133154
sequence = traverse id
134155

156+
instance traversableWithIndexList :: TraversableWithIndex Int List where
157+
traverseWithIndex f =
158+
foldrWithIndex (\i a b -> cons <$> f i a <*> b) (pure nil)
159+
135160
instance applyList :: Apply List where
136161
apply = ap
137162

src/Data/List/Types.purs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import Control.Extend (class Extend)
1010
import Control.MonadPlus (class MonadPlus)
1111
import Control.MonadZero (class MonadZero)
1212
import Control.Plus (class Plus)
13-
1413
import Data.Eq (class Eq1, eq1)
1514
import Data.Foldable (class Foldable, foldl, foldr, intercalate)
15+
import Data.FoldableWithIndex (class FoldableWithIndex, foldlWithIndex, foldrWithIndex)
16+
import Data.FunctorWithIndex (class FunctorWithIndex)
1617
import Data.Maybe (Maybe(..))
1718
import Data.Monoid (class Monoid, mempty)
1819
import Data.Newtype (class Newtype)
@@ -22,7 +23,8 @@ import Data.Ord (class Ord1, compare1)
2223
import Data.Semigroup.Foldable (class Foldable1)
2324
import Data.Semigroup.Traversable (class Traversable1, traverse1)
2425
import Data.Traversable (class Traversable, traverse)
25-
import Data.Tuple (Tuple(..))
26+
import Data.TraversableWithIndex (class TraversableWithIndex)
27+
import Data.Tuple (Tuple(..), snd)
2628
import Data.Unfoldable (class Unfoldable)
2729

2830
data List a = Nil | Cons a (List a)
@@ -67,19 +69,36 @@ instance monoidList :: Monoid (List a) where
6769
instance functorList :: Functor List where
6870
map f = foldr (\x acc -> f x : acc) Nil
6971

72+
instance functorWithIndexList :: FunctorWithIndex Int List where
73+
mapWithIndex f = foldrWithIndex (\i x acc -> f i x : acc) Nil
74+
7075
instance foldableList :: Foldable List where
71-
foldr f b = foldl (flip f) b <<< rev Nil
76+
foldr f b = foldl (flip f) b <<< rev
7277
where
73-
rev acc = case _ of
74-
Nil -> acc
75-
a : as -> rev (a : acc) as
78+
rev = foldl (flip Cons) Nil
7679
foldl f = go
7780
where
7881
go b = case _ of
7982
Nil -> b
8083
a : as -> go (f b a) as
8184
foldMap f = foldl (\acc -> append acc <<< f) mempty
8285

86+
instance foldableWithIndexList :: FoldableWithIndex Int List where
87+
foldrWithIndex f b xs =
88+
-- as we climb the reversed list, we decrement the index
89+
snd $ foldl
90+
(\(Tuple i b') a -> Tuple (i - 1) (f (i - 1) a b'))
91+
(Tuple len b)
92+
revList
93+
where
94+
Tuple len revList = rev (Tuple 0 Nil) xs
95+
where
96+
-- As we create our reversed list, we count elements.
97+
rev = foldl (\(Tuple i acc) a -> Tuple (i + 1) (a : acc))
98+
foldlWithIndex f acc =
99+
snd <<< foldl (\(Tuple i b) a -> Tuple (i + 1) (f i b a)) (Tuple 0 acc)
100+
foldMapWithIndex f = foldlWithIndex (\i acc -> append acc <<< f i) mempty
101+
83102
instance unfoldableList :: Unfoldable List where
84103
unfoldr f b = go b Nil
85104
where
@@ -91,6 +110,13 @@ instance traversableList :: Traversable List where
91110
traverse f = map (foldl (flip (:)) Nil) <<< foldl (\acc -> lift2 (flip (:)) acc <<< f) (pure Nil)
92111
sequence = traverse id
93112

113+
instance traversableWithIndexList :: TraversableWithIndex Int List where
114+
traverseWithIndex f =
115+
map rev
116+
<<< foldlWithIndex (\i acc -> lift2 (flip (:)) acc <<< f i) (pure Nil)
117+
where
118+
rev = foldl (flip Cons) Nil
119+
94120
instance applyList :: Apply List where
95121
apply Nil _ = Nil
96122
apply (f : fs) xs = (f <$> xs) <> (fs <*> xs)

test/Test/Data/List.purs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
module Test.Data.List (testList) where
22

33
import Prelude
4-
import Data.List.NonEmpty as NEL
4+
55
import Control.Monad.Eff (Eff)
66
import Control.Monad.Eff.Console (CONSOLE, log)
77
import Data.Foldable (foldMap, foldl)
8+
import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex)
89
import Data.List (List(..), (..), stripPrefix, Pattern(..), length, range, foldM, unzip, zip, zipWithA, zipWith, intersectBy, intersect, (\\), deleteBy, delete, unionBy, union, nubBy, nub, groupBy, group', group, partition, span, dropWhile, drop, dropEnd, takeWhile, take, takeEnd, sortBy, sort, catMaybes, mapMaybe, filterM, filter, concat, concatMap, reverse, alterAt, modifyAt, updateAt, deleteAt, insertAt, findLastIndex, findIndex, elemLastIndex, elemIndex, (!!), uncons, unsnoc, init, tail, last, head, insertBy, insert, snoc, null, singleton, fromFoldable, transpose, mapWithIndex, (:))
10+
import Data.List.NonEmpty as NEL
911
import Data.Maybe (Maybe(..), isNothing, fromJust)
1012
import Data.Monoid.Additive (Additive(..))
1113
import Data.NonEmpty ((:|))
1214
import Data.Traversable (traverse)
15+
import Data.TraversableWithIndex (traverseWithIndex)
1316
import Data.Tuple (Tuple(..))
1417
import Data.Unfoldable (replicate, replicateA, unfoldr)
1518
import Partial.Unsafe (unsafePartial)
@@ -324,12 +327,30 @@ testList = do
324327
log "foldl should be stack-safe"
325328
void $ pure $ foldl (+) 0 $ range 1 100000
326329

330+
log "foldlWithIndex should be correct"
331+
assert $ foldlWithIndex (\i b _ -> i + b) 0 (range 0 10000) == 50005000
332+
333+
log "foldlWithIndex should be stack-safe"
334+
void $ pure $ foldlWithIndex (\i b _ -> i + b) 0 $ range 0 100000
335+
336+
log "foldrWithIndex should be correct"
337+
assert $ foldrWithIndex (\i _ b -> i + b) 0 (range 0 10000) == 50005000
338+
339+
log "foldrWithIndex should be stack-safe"
340+
void $ pure $ foldrWithIndex (\i _ b -> i + b) 0 $ range 0 100000
341+
327342
log "foldMap should be stack-safe"
328343
void $ pure $ foldMap Additive $ range 1 100000
329344

330345
log "foldMap should be left-to-right"
331346
assert $ foldMap show (range 1 5) == "12345"
332347

348+
log "foldMapWithIndex should be stack-safe"
349+
void $ pure $ foldMapWithIndex (\i _ -> Additive i) $ range 1 100000
350+
351+
log "foldMapWithIndex should be left-to-right"
352+
assert $ foldMapWithIndex (\i _ -> show i) (fromFoldable [0, 0, 0]) == "012"
353+
333354
log "unfoldable replicate should be stack-safe"
334355
void $ pure $ length $ replicate 100000 1
335356

@@ -354,6 +375,13 @@ testList = do
354375
let xs = fromFoldable (range 1 100000)
355376
assert $ traverse Just xs == Just xs
356377

378+
log "traverseWithIndex should be stack-safe"
379+
assert $ traverseWithIndex (const Just) xs == Just xs
380+
381+
log "traverseWithIndex should be correct"
382+
assert $ traverseWithIndex (\i a -> Just $ i + a) (fromFoldable [2, 2, 2])
383+
== Just (fromFoldable [2, 3, 4])
384+
357385
log "append should concatenate two lists"
358386
assert $ (l [1, 2]) <> (l [3, 4]) == (l [1, 2, 3, 4])
359387

test/Test/Data/List/Lazy.purs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ import Prelude
55
import Control.Lazy (defer)
66
import Control.Monad.Eff (Eff)
77
import Control.Monad.Eff.Console (CONSOLE, log)
8-
8+
import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex)
9+
import Data.FunctorWithIndex (mapWithIndex)
910
import Data.Lazy as Z
1011
import Data.List.Lazy (List, nil, stripPrefix, Pattern(..), cons, foldl, foldr, foldMap, singleton, transpose, take, iterate, filter, uncons, foldM, foldrLazy, range, unzip, zip, length, zipWithA, replicate, repeat, zipWith, intersectBy, intersect, deleteBy, delete, unionBy, union, nubBy, nub, groupBy, group, partition, 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, (:), (\\), (!!))
1112
import Data.List.Lazy.NonEmpty as NEL
1213
import Data.Maybe (Maybe(..), isNothing, fromJust)
1314
import Data.Monoid.Additive (Additive(..))
1415
import Data.NonEmpty ((:|))
1516
import Data.Traversable (traverse)
17+
import Data.TraversableWithIndex (traverseWithIndex)
1618
import Data.Tuple (Tuple(..))
17-
1819
import Partial.Unsafe (unsafePartial)
19-
2020
import Test.Assert (ASSERT, assert)
2121

2222
testListLazy :: forall eff. Eff (assert :: ASSERT, console :: CONSOLE | eff) Unit
@@ -48,9 +48,34 @@ testListLazy = do
4848
log "foldMap should be left-to-right"
4949
assert $ foldMap show (range 1 5) == "12345"
5050

51+
log "foldlWithIndex should be correct"
52+
assert $ foldlWithIndex (\i b _ -> i + b) 0 (range 0 10000) == 50005000
53+
54+
log "foldlWithIndex should be stack-safe"
55+
void $ pure $ foldlWithIndex (\i b _ -> i + b) 0 $ range 0 100000
56+
57+
log "foldrWithIndex should be correct"
58+
assert $ foldrWithIndex (\i _ b -> i + b) 0 (range 0 10000) == 50005000
59+
60+
log "foldrWithIndex should be stack-safe"
61+
void $ pure $ foldrWithIndex (\i _ b -> i + b) 0 $ range 0 100000
62+
63+
log "foldMapWithIndex should be stack-safe"
64+
void $ pure $ foldMapWithIndex (\i _ -> Additive i) $ range 1 100000
65+
66+
log "foldMapWithIndex should be left-to-right"
67+
assert $ foldMapWithIndex (\i _ -> show i) (fromFoldable [0, 0, 0]) == "012"
68+
5169
log "traverse should be stack-safe"
5270
assert $ ((traverse Just longList) >>= last) == last longList
5371

72+
log "traverseWithIndex should be stack-safe"
73+
assert $ traverseWithIndex (const Just) longList == Just longList
74+
75+
log "traverseWithIndex should be correct"
76+
assert $ traverseWithIndex (\i a -> Just $ i + a) (fromFoldable [2, 2, 2])
77+
== Just (fromFoldable [2, 3, 4])
78+
5479
log "bind should be stack-safe"
5580
void $ pure $ last $ longList >>= pure
5681

@@ -228,6 +253,9 @@ testListLazy = do
228253
log "catMaybe should take an list of Maybe values and throw out Nothings"
229254
assert $ catMaybes (l [Nothing, Just 2, Nothing, Just 4]) == l [2, 4]
230255

256+
log "mapWithIndex should take a list of values and apply a function which also takes the index into account"
257+
assert $ mapWithIndex (\x ix -> x + ix) (fromFoldable [0, 1, 2, 3]) == fromFoldable [0, 2, 4, 6]
258+
231259
-- log "sort should reorder a list into ascending order based on the result of compare"
232260
-- assert $ sort (l [1, 3, 2, 5, 6, 4]) == l [1, 2, 3, 4, 5, 6]
233261

0 commit comments

Comments
 (0)