Skip to content

Commit

Permalink
indexed foldable-traversable instances (#134)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
matthewleon authored and paf31 committed Nov 30, 2017
1 parent 6c8aaad commit ab3189f
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 17 deletions.
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"purescript-tailrec": "^3.3.0",
"purescript-unfoldable": "^3.0.0",
"purescript-partial": "^1.0.0",
"purescript-foldable-traversable": "^3.3.0"
"purescript-foldable-traversable": "^3.4.0"
},
"devDependencies": {
"purescript-arrays": "^4.0.0",
Expand Down
8 changes: 4 additions & 4 deletions src/Data/List.purs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM, tailRecM2)

import Data.Bifunctor (bimap)
import Data.Foldable (class Foldable, foldr, any, foldl)
import Data.FunctorWithIndex (mapWithIndex) as FWI
import Data.List.Types (List(..), (:))
import Data.List.Types (NonEmptyList(..)) as NEL
import Data.Maybe (Maybe(..))
Expand Down Expand Up @@ -426,11 +427,10 @@ catMaybes = mapMaybe id


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

--------------------------------------------------------------------------------
-- Sorting ---------------------------------------------------------------------
Expand Down
29 changes: 27 additions & 2 deletions src/Data/List/Lazy/Types.purs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import Control.Lazy as Z
import Control.MonadPlus (class MonadPlus)
import Control.MonadZero (class MonadZero)
import Control.Plus (class Plus)

import Data.Eq (class Eq1, eq1)
import Data.Foldable (class Foldable, foldMap, foldl, foldr)
import Data.FoldableWithIndex (class FoldableWithIndex, foldlWithIndex, foldrWithIndex)
import Data.FunctorWithIndex (class FunctorWithIndex)
import Data.Lazy (Lazy, defer, force)
import Data.Maybe (Maybe(..))
import Data.Monoid (class Monoid, mempty)
Expand All @@ -21,7 +22,8 @@ import Data.NonEmpty (NonEmpty, (:|))
import Data.NonEmpty as NE
import Data.Ord (class Ord1, compare1)
import Data.Traversable (class Traversable, traverse, sequence)
import Data.Tuple (Tuple(..))
import Data.TraversableWithIndex (class TraversableWithIndex)
import Data.Tuple (Tuple(..), snd)
import Data.Unfoldable (class Unfoldable)

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

instance functorWithIndexList :: FunctorWithIndex Int List where
mapWithIndex f = foldrWithIndex (\i x acc -> f i x : acc) nil

instance foldableList :: Foldable List where
-- calls foldl on the reversed list
foldr op z xs = foldl (flip op) z (rev xs) where
Expand All @@ -120,6 +125,22 @@ instance foldableList :: Foldable List where

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

instance foldableWithIndexList :: FoldableWithIndex Int List where
foldrWithIndex f b xs =
-- as we climb the reversed list, we decrement the index
snd $ foldl
(\(Tuple i b') a -> Tuple (i - 1) (f (i - 1) a b'))
(Tuple len b)
revList
where
Tuple len revList = rev (Tuple 0 nil) xs
where
-- As we create our reversed list, we count elements.
rev = foldl (\(Tuple i acc) a -> Tuple (i + 1) (a : acc))
foldlWithIndex f acc =
snd <<< foldl (\(Tuple i b) a -> Tuple (i + 1) (f i b a)) (Tuple 0 acc)
foldMapWithIndex f = foldlWithIndex (\i acc -> append acc <<< f i) mempty

instance unfoldableList :: Unfoldable List where
unfoldr = go where
go f b = Z.defer \_ -> case f b of
Expand All @@ -132,6 +153,10 @@ instance traversableList :: Traversable List where

sequence = traverse id

instance traversableWithIndexList :: TraversableWithIndex Int List where
traverseWithIndex f =
foldrWithIndex (\i a b -> cons <$> f i a <*> b) (pure nil)

instance applyList :: Apply List where
apply = ap

Expand Down
38 changes: 32 additions & 6 deletions src/Data/List/Types.purs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import Control.Extend (class Extend)
import Control.MonadPlus (class MonadPlus)
import Control.MonadZero (class MonadZero)
import Control.Plus (class Plus)

import Data.Eq (class Eq1, eq1)
import Data.Foldable (class Foldable, foldl, foldr, intercalate)
import Data.FoldableWithIndex (class FoldableWithIndex, foldlWithIndex, foldrWithIndex)
import Data.FunctorWithIndex (class FunctorWithIndex)
import Data.Maybe (Maybe(..))
import Data.Monoid (class Monoid, mempty)
import Data.Newtype (class Newtype)
Expand All @@ -22,7 +23,8 @@ import Data.Ord (class Ord1, compare1)
import Data.Semigroup.Foldable (class Foldable1)
import Data.Semigroup.Traversable (class Traversable1, traverse1)
import Data.Traversable (class Traversable, traverse)
import Data.Tuple (Tuple(..))
import Data.TraversableWithIndex (class TraversableWithIndex)
import Data.Tuple (Tuple(..), snd)
import Data.Unfoldable (class Unfoldable)

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

instance functorWithIndexList :: FunctorWithIndex Int List where
mapWithIndex f = foldrWithIndex (\i x acc -> f i x : acc) Nil

instance foldableList :: Foldable List where
foldr f b = foldl (flip f) b <<< rev Nil
foldr f b = foldl (flip f) b <<< rev
where
rev acc = case _ of
Nil -> acc
a : as -> rev (a : acc) as
rev = foldl (flip Cons) Nil
foldl f = go
where
go b = case _ of
Nil -> b
a : as -> go (f b a) as
foldMap f = foldl (\acc -> append acc <<< f) mempty

instance foldableWithIndexList :: FoldableWithIndex Int List where
foldrWithIndex f b xs =
-- as we climb the reversed list, we decrement the index
snd $ foldl
(\(Tuple i b') a -> Tuple (i - 1) (f (i - 1) a b'))
(Tuple len b)
revList
where
Tuple len revList = rev (Tuple 0 Nil) xs
where
-- As we create our reversed list, we count elements.
rev = foldl (\(Tuple i acc) a -> Tuple (i + 1) (a : acc))
foldlWithIndex f acc =
snd <<< foldl (\(Tuple i b) a -> Tuple (i + 1) (f i b a)) (Tuple 0 acc)
foldMapWithIndex f = foldlWithIndex (\i acc -> append acc <<< f i) mempty

instance unfoldableList :: Unfoldable List where
unfoldr f b = go b Nil
where
Expand All @@ -91,6 +110,13 @@ instance traversableList :: Traversable List where
traverse f = map (foldl (flip (:)) Nil) <<< foldl (\acc -> lift2 (flip (:)) acc <<< f) (pure Nil)
sequence = traverse id

instance traversableWithIndexList :: TraversableWithIndex Int List where
traverseWithIndex f =
map rev
<<< foldlWithIndex (\i acc -> lift2 (flip (:)) acc <<< f i) (pure Nil)
where
rev = foldl (flip Cons) Nil

instance applyList :: Apply List where
apply Nil _ = Nil
apply (f : fs) xs = (f <$> xs) <> (fs <*> xs)
Expand Down
30 changes: 29 additions & 1 deletion test/Test/Data/List.purs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
module Test.Data.List (testList) where

import Prelude
import Data.List.NonEmpty as NEL

import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Data.Foldable (foldMap, foldl)
import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex)
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, (:))
import Data.List.NonEmpty as NEL
import Data.Maybe (Maybe(..), isNothing, fromJust)
import Data.Monoid.Additive (Additive(..))
import Data.NonEmpty ((:|))
import Data.Traversable (traverse)
import Data.TraversableWithIndex (traverseWithIndex)
import Data.Tuple (Tuple(..))
import Data.Unfoldable (replicate, replicateA, unfoldr)
import Partial.Unsafe (unsafePartial)
Expand Down Expand Up @@ -324,12 +327,30 @@ testList = do
log "foldl should be stack-safe"
void $ pure $ foldl (+) 0 $ range 1 100000

log "foldlWithIndex should be correct"
assert $ foldlWithIndex (\i b _ -> i + b) 0 (range 0 10000) == 50005000

log "foldlWithIndex should be stack-safe"
void $ pure $ foldlWithIndex (\i b _ -> i + b) 0 $ range 0 100000

log "foldrWithIndex should be correct"
assert $ foldrWithIndex (\i _ b -> i + b) 0 (range 0 10000) == 50005000

log "foldrWithIndex should be stack-safe"
void $ pure $ foldrWithIndex (\i _ b -> i + b) 0 $ range 0 100000

log "foldMap should be stack-safe"
void $ pure $ foldMap Additive $ range 1 100000

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

log "foldMapWithIndex should be stack-safe"
void $ pure $ foldMapWithIndex (\i _ -> Additive i) $ range 1 100000

log "foldMapWithIndex should be left-to-right"
assert $ foldMapWithIndex (\i _ -> show i) (fromFoldable [0, 0, 0]) == "012"

log "unfoldable replicate should be stack-safe"
void $ pure $ length $ replicate 100000 1

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

log "traverseWithIndex should be stack-safe"
assert $ traverseWithIndex (const Just) xs == Just xs

log "traverseWithIndex should be correct"
assert $ traverseWithIndex (\i a -> Just $ i + a) (fromFoldable [2, 2, 2])
== Just (fromFoldable [2, 3, 4])

log "append should concatenate two lists"
assert $ (l [1, 2]) <> (l [3, 4]) == (l [1, 2, 3, 4])

Expand Down
34 changes: 31 additions & 3 deletions test/Test/Data/List/Lazy.purs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import Prelude
import Control.Lazy (defer)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)

import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex)
import Data.FunctorWithIndex (mapWithIndex)
import Data.Lazy as Z
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, (:), (\\), (!!))
import Data.List.Lazy.NonEmpty as NEL
import Data.Maybe (Maybe(..), isNothing, fromJust)
import Data.Monoid.Additive (Additive(..))
import Data.NonEmpty ((:|))
import Data.Traversable (traverse)
import Data.TraversableWithIndex (traverseWithIndex)
import Data.Tuple (Tuple(..))

import Partial.Unsafe (unsafePartial)

import Test.Assert (ASSERT, assert)

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

log "foldlWithIndex should be correct"
assert $ foldlWithIndex (\i b _ -> i + b) 0 (range 0 10000) == 50005000

log "foldlWithIndex should be stack-safe"
void $ pure $ foldlWithIndex (\i b _ -> i + b) 0 $ range 0 100000

log "foldrWithIndex should be correct"
assert $ foldrWithIndex (\i _ b -> i + b) 0 (range 0 10000) == 50005000

log "foldrWithIndex should be stack-safe"
void $ pure $ foldrWithIndex (\i _ b -> i + b) 0 $ range 0 100000

log "foldMapWithIndex should be stack-safe"
void $ pure $ foldMapWithIndex (\i _ -> Additive i) $ range 1 100000

log "foldMapWithIndex should be left-to-right"
assert $ foldMapWithIndex (\i _ -> show i) (fromFoldable [0, 0, 0]) == "012"

log "traverse should be stack-safe"
assert $ ((traverse Just longList) >>= last) == last longList

log "traverseWithIndex should be stack-safe"
assert $ traverseWithIndex (const Just) longList == Just longList

log "traverseWithIndex should be correct"
assert $ traverseWithIndex (\i a -> Just $ i + a) (fromFoldable [2, 2, 2])
== Just (fromFoldable [2, 3, 4])

log "bind should be stack-safe"
void $ pure $ last $ longList >>= pure

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

log "mapWithIndex should take a list of values and apply a function which also takes the index into account"
assert $ mapWithIndex (\x ix -> x + ix) (fromFoldable [0, 1, 2, 3]) == fromFoldable [0, 2, 4, 6]

-- log "sort should reorder a list into ascending order based on the result of compare"
-- assert $ sort (l [1, 3, 2, 5, 6, 4]) == l [1, 2, 3, 4, 5, 6]

Expand Down

0 comments on commit ab3189f

Please sign in to comment.