diff --git a/CHANGELOG.md b/CHANGELOG.md index 553e776..9150925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ Notable changes to this project are documented in this file. The format is based ## [Unreleased] Breaking changes: +- Rename `scanrLazy` to `scanlLazy` and fix parameter ordering (#161) +- Rename `group'` to `groupAll` (#182) +- Change `Alt ZipList` to satisfy distributivity (#150) New features: diff --git a/src/Data/List.purs b/src/Data/List.purs index db9c1cd..39ee182 100644 --- a/src/Data/List.purs +++ b/src/Data/List.purs @@ -18,6 +18,7 @@ module Data.List , someRec , many , manyRec + -- , replicate -- questionable specialization , null , length @@ -95,6 +96,13 @@ module Data.List , foldM , module Exports + + -- additions + , appendFoldable + + , cons' + , snoc' + ) where import Prelude @@ -109,7 +117,7 @@ import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, f import Data.FunctorWithIndex (mapWithIndex) as FWI import Data.List.Internal (emptySet, insertAndLookupBy) import Data.List.Types (List(..), (:)) -import Data.List.Types (NonEmptyList(..)) as NEL +import Data.List.Types as NEL import Data.Maybe (Maybe(..)) import Data.Newtype (class Newtype) import Data.NonEmpty ((:|)) @@ -119,6 +127,18 @@ import Data.Tuple (Tuple(..)) import Data.Unfoldable (class Unfoldable, unfoldr) import Prim.TypeError (class Warn, Text) + +---------- Additions + +appendFoldable :: forall t a. Foldable t => List a -> t a -> List a +appendFoldable xs ys = xs <> fromFoldable ys + +cons' :: forall a. a -> NEL.NonEmptyList a -> List a +cons' x xs = Cons x $ NEL.toList xs + +snoc' :: forall a. NEL.NonEmptyList a -> a -> List a +snoc' xs x = snoc (NEL.toList xs) x + -- | Convert a list into any unfoldable structure. -- | -- | Running time: `O(n)` @@ -180,6 +200,15 @@ manyRec p = tailRecM go Nil aa <- (Loop <$> p) <|> pure (Done unit) pure $ bimap (_ : acc) (\_ -> reverse acc) aa +-- Questionable whether this should be specialized +-- -- | Create a list containing a value repeated n times +-- replicate :: forall a. Int -> a -> List a +-- replicate num x = go num Nil +-- where +-- go n xs | n < 1 = xs +-- | otherwise = go (n - 1) (x : xs) + + -------------------------------------------------------------------------------- -- List size ------------------------------------------------------------------- -------------------------------------------------------------------------------- @@ -626,17 +655,16 @@ groupBy _ Nil = Nil groupBy eq (x : xs) = case span (eq x) xs of { init: ys, rest: zs } -> NEL.NonEmptyList (x :| ys) : groupBy eq zs --- | Group equal elements of a list into lists, using the specified --- | equivalence relation to determine equality. --- | --- | For example, +-- | Sort, then group equal elements of a list into lists, using the provided comparison function. -- | -- | ```purescript --- | groupAllBy (\a b -> odd a && odd b) (1 : 3 : 2 : 4 : 3 : 3 : Nil) == --- | (NonEmptyList (NonEmpty 1 Nil)) : (NonEmptyList (NonEmpty 2 Nil)) : (NonEmptyList (NonEmpty 3 (3 : 3 : Nil))) : (NonEmptyList (NonEmpty 4 Nil)) : Nil +-- | groupAllBy (compare `on` (_ `div` 10)) (32 : 31 : 21 : 22 : 11 : 33 : Nil) == +-- | NonEmptyList (11 :| Nil) : NonEmptyList (21 :| 22 : Nil) : NonEmptyList (32 :| 31 : 33) : Nil -- | ``` -groupAllBy :: forall a. Ord a => (a -> a -> Boolean) -> List a -> List (NEL.NonEmptyList a) -groupAllBy p = groupBy p <<< sort +-- | +-- | Running time: `O(n log n)` +groupAllBy :: forall a. (a -> a -> Ordering) -> List a -> List (NEL.NonEmptyList a) +groupAllBy p = groupBy (\x y -> p x y == EQ) <<< sortBy p -- | Returns a lists of elements which do and do not satisfy a predicate. -- | diff --git a/src/Data/List/Lazy.purs b/src/Data/List/Lazy.purs index 8821753..da89428 100644 --- a/src/Data/List/Lazy.purs +++ b/src/Data/List/Lazy.purs @@ -61,12 +61,14 @@ module Data.List.Lazy , stripPrefix , slice , take + , takeEnd , takeWhile , drop , dropWhile , span , group -- , group' + , groupAll , groupBy , partition @@ -94,6 +96,22 @@ module Data.List.Lazy , scanlLazy , module Exports + + -- additions + , appendFoldable + , someRec + , sort + , sortBy + + , cons' + , dropEnd + , groupAllBy + , snoc' + , manyRec + + , replicate1 + , replicate1M + ) where import Prelude @@ -102,6 +120,7 @@ import Control.Alt ((<|>)) import Control.Alternative (class Alternative) import Control.Lazy as Z import Control.Monad.Rec.Class as Rec +import Control.Monad.Rec.Class (class MonadRec) import Data.Foldable (class Foldable, foldr, any, foldl) import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports import Data.Lazy (defer) @@ -115,6 +134,36 @@ import Data.Traversable (scanl, scanr) as Exports import Data.Traversable (sequence) import Data.Tuple (Tuple(..)) import Data.Unfoldable (class Unfoldable, unfoldr) +import Partial.Unsafe (unsafeCrashWith) + +-- Additions +appendFoldable :: forall t a. Foldable t => List a -> t a -> List a +appendFoldable _ _ = unsafeCrashWith "todo appendFoldable for Lazy List" +someRec :: forall f a. MonadRec f => Alternative f => f a -> f (List a) +someRec _ = unsafeCrashWith "todo someRec for Lazy List" +sort :: forall a. Ord a => List a -> List a +sort _ = unsafeCrashWith "todo sort for Lazy List" +sortBy :: forall a. (a -> a -> Ordering) -> List a -> List a +sortBy _ _ = unsafeCrashWith "todo sortBy for Lazy List" + +cons' :: forall a. a -> NEL.NonEmptyList a -> List a +cons' _ _ = unsafeCrashWith "todo cons' for Lazy List" +dropEnd :: forall a. Int -> List a -> List a +dropEnd _ _ = unsafeCrashWith "todo dropEnd for Lazy List" +groupAllBy :: forall a. (a -> a -> Ordering) -> List a -> List (NEL.NonEmptyList a) +groupAllBy _ _ = unsafeCrashWith "todo groupAllBy for Lazy List" +snoc' :: forall a. NEL.NonEmptyList a -> a -> List a +snoc' _ _ = unsafeCrashWith "todo snoc' for Lazy List" + +manyRec :: forall f a. MonadRec f => Alternative f => f a -> f (List a) +manyRec _ = unsafeCrashWith "todo manyRec for Lazy List" + +-- Specialized from Unfoldable1's replicate1 / replicate1A +replicate1 :: forall a. Int -> a -> List a +replicate1 _ _ = unsafeCrashWith "todo replicate1 for Lazy List" + +replicate1M :: forall m a. Monad m => Int -> m a -> m (List a) +replicate1M _ _ = unsafeCrashWith "todo replicate1M for Lazy List" -- | Convert a list into any unfoldable structure. -- | @@ -506,6 +555,12 @@ take n = if n <= 0 go _ Nil = Nil go n' (Cons x xs) = Cons x (take (n' - 1) xs) +-- | Take the specified number of elements from the end of a list. +-- | +-- | Running time: Todo +takeEnd :: forall a. Int -> List a -> List a +takeEnd _ _ = unsafeCrashWith "todo takeEnd for Lazy List" + -- | Take those elements from the front of a list which match a predicate. -- | -- | Running time (worst case): `O(n)` @@ -521,7 +576,7 @@ takeWhile p = List <<< map go <<< unwrap drop :: forall a. Int -> List a -> List a drop n = List <<< map (go n) <<< unwrap where - go 0 xs = xs + go n' xs | n' < 1 = xs go _ Nil = Nil go n' (Cons _ xs) = go (n' - 1) (step xs) @@ -566,6 +621,14 @@ span p xs = group :: forall a. Eq a => List a -> List (NEL.NonEmptyList a) group = groupBy (==) +-- | Group equal elements of a list into lists. +-- | +-- | Todo - fix documentation mismatch of above `group` with non-lazy version. +-- | ``` +groupAll :: forall a. Ord a => List a -> List (NEL.NonEmptyList a) +groupAll = unsafeCrashWith "todo groupAll for Lazy List" +--groupAll = group <<< sort + -- | Group equal, consecutive elements of a list into lists, using the specified -- | equivalence relation to determine equality. -- | diff --git a/src/Data/List/Lazy/NonEmpty.purs b/src/Data/List/Lazy/NonEmpty.purs index 20ef04a..33f4d87 100644 --- a/src/Data/List/Lazy/NonEmpty.purs +++ b/src/Data/List/Lazy/NonEmpty.purs @@ -13,21 +13,280 @@ module Data.List.Lazy.NonEmpty , init , uncons , length + , concat , concatMap , appendFoldable + -- additions + , catMaybes + , cons + , drop + , dropWhile + , elemIndex + , elemLastIndex + , filter + , filterM + , findIndex + , findLastIndex + , foldM + , group + , groupAll + , groupBy + , index + , insertAt + , intersect + , intersectBy + , mapMaybe + , modifyAt + , nubEq + , nubByEq + , partition + , range + , reverse + , snoc + , snoc' + , span + , take + , takeEnd + , takeWhile + , union + , unionBy + , unzip + , updateAt + , zip + , zipWith + , zipWithA + + , insert + , insertBy + , nub + , nubBy + , Pattern(..) + , replicate + , replicateM + , some + , someRec + , sort + , sortBy + , transpose + + , cons' + , delete + , deleteBy + , difference + , dropEnd + , groupAllBy + , slice + , stripPrefix + , deleteAt + , alterAt + + , cycle + , foldrLazy + , scanlLazy + + , replicate1 + , replicate1M + ) where import Prelude +import Control.Alternative (class Alternative) +import Control.Lazy (class Lazy) +import Control.Monad.Rec.Class (class MonadRec) import Data.Foldable (class Foldable) import Data.Lazy (force, defer) import Data.List.Lazy ((:)) import Data.List.Lazy as L import Data.List.Lazy.Types (NonEmptyList(..)) import Data.Maybe (Maybe(..), maybe, fromMaybe) +import Data.Newtype (class Newtype) import Data.NonEmpty ((:|)) import Data.Tuple (Tuple(..)) import Data.Unfoldable (class Unfoldable, unfoldr) +import Partial.Unsafe (unsafeCrashWith) + +--- Sorted additions ------ + +-- | Filter a list of optional values, keeping only the elements which contain +-- | a value. +catMaybes :: forall a. NonEmptyList (Maybe a) -> L.List a +catMaybes _ = unsafeCrashWith "todo catMaybes for Lazy NonEmptyList" +--catMaybes = mapMaybe identity + +cons :: forall a. a -> NonEmptyList a -> NonEmptyList a +cons _ _ = unsafeCrashWith "todo cons for Lazy NonEmptyList" + +-- | Drop the specified number of elements from the front of a list. +drop :: forall a. Int -> NonEmptyList a -> L.List a +drop _ _ = unsafeCrashWith "todo drop for Lazy NonEmptyList" + +dropWhile :: forall a. (a -> Boolean) -> NonEmptyList a -> L.List a +dropWhile _ _ = unsafeCrashWith "todo dropWhile for Lazy NonEmptyList" + +elemIndex :: forall a. Eq a => a -> NonEmptyList a -> Maybe Int +elemIndex _ _ = unsafeCrashWith "todo elemIndex for Lazy NonEmptyList" + +elemLastIndex :: forall a. Eq a => a -> NonEmptyList a -> Maybe Int +elemLastIndex _ _ = unsafeCrashWith "todo elemLastIndex for Lazy NonEmptyList" + +filter :: forall a. (a -> Boolean) -> NonEmptyList a -> L.List a +filter _ _ = unsafeCrashWith "todo filter for Lazy NonEmptyList" + +filterM :: forall m a. Monad m => (a -> m Boolean) -> NonEmptyList a -> m (L.List a) +filterM _ _ = unsafeCrashWith "todo filterM for Lazy NonEmptyList" + +findIndex :: forall a. (a -> Boolean) -> NonEmptyList a -> Maybe Int +findIndex _ _ = unsafeCrashWith "todo findIndex for Lazy NonEmptyList" + +findLastIndex :: forall a. (a -> Boolean) -> NonEmptyList a -> Maybe Int +findLastIndex _ _ = unsafeCrashWith "todo findLastIndex for Lazy NonEmptyList" + +foldM :: forall m a b. Monad m => (b -> a -> m b) -> b -> NonEmptyList a -> m b +foldM _ _ _ = unsafeCrashWith "todo foldM for Lazy NonEmptyList" + +group :: forall a. Eq a => NonEmptyList a -> NonEmptyList (NonEmptyList a) +group _ = unsafeCrashWith "todo group for Lazy NonEmptyList" + +groupAll :: forall a. Ord a => NonEmptyList a -> NonEmptyList (NonEmptyList a) +groupAll _ = unsafeCrashWith "todo groupAll for Lazy NonEmptyList" + +groupBy :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList (NonEmptyList a) +groupBy _ _ = unsafeCrashWith "todo groupBy for Lazy NonEmptyList" + +index :: forall a. NonEmptyList a -> Int -> Maybe a +index _ _ = unsafeCrashWith "todo index for Lazy NonEmptyList" + +insertAt :: forall a. Int -> a -> NonEmptyList a -> NonEmptyList a +insertAt _ _ _ = unsafeCrashWith "todo insertAt for Lazy NonEmptyList" + +intersect :: forall a. Eq a => NonEmptyList a -> NonEmptyList a -> NonEmptyList a +intersect _ _ = unsafeCrashWith "todo intersect for Lazy NonEmptyList" + +intersectBy :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList a -> NonEmptyList a +intersectBy _ _ _ = unsafeCrashWith "todo intersectBy for Lazy NonEmptyList" + +mapMaybe :: forall a b. (a -> Maybe b) -> NonEmptyList a -> L.List b +mapMaybe _ _ = unsafeCrashWith "todo mapMaybe for Lazy NonEmptyList" + +modifyAt :: forall a. Int -> (a -> a) -> NonEmptyList a -> NonEmptyList a +modifyAt _ _ _ = unsafeCrashWith "todo modifyAt for Lazy NonEmptyList" + +nubEq :: forall a. Eq a => NonEmptyList a -> NonEmptyList a +nubEq _ = unsafeCrashWith "todo nubEq for Lazy NonEmptyList" + +nubByEq :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList a +nubByEq _ _ = unsafeCrashWith "todo nubByEq for Lazy NonEmptyList" + +partition :: forall a. (a -> Boolean) -> NonEmptyList a -> { yes :: L.List a, no :: L.List a } +partition _ _ = unsafeCrashWith "todo partition for Lazy NonEmptyList" +range :: Int -> Int -> NonEmptyList Int +range _ _ = unsafeCrashWith "todo range for Lazy NonEmptyList" + +reverse :: forall a. NonEmptyList a -> NonEmptyList a +reverse _ = unsafeCrashWith "todo reverse for Lazy NonEmptyList" + +snoc :: forall a. NonEmptyList a -> a -> NonEmptyList a +snoc _ _ = unsafeCrashWith "todo snoc for Lazy NonEmptyList" + +snoc' :: forall a. L.List a -> a -> NonEmptyList a +snoc' _ _ = unsafeCrashWith "todo snoc' for Lazy NonEmptyList" + +span :: forall a. (a -> Boolean) -> NonEmptyList a -> { init :: L.List a, rest :: L.List a } +span _ _ = unsafeCrashWith "todo span for Lazy NonEmptyList" + +take :: forall a. Int -> NonEmptyList a -> L.List a +take _ _ = unsafeCrashWith "todo take for Lazy NonEmptyList" + +takeEnd :: forall a. Int -> NonEmptyList a -> L.List a +takeEnd _ _ = unsafeCrashWith "todo takeEnd for Lazy NonEmptyList" + +takeWhile :: forall a. (a -> Boolean) -> NonEmptyList a -> L.List a +takeWhile _ _ = unsafeCrashWith "todo takeWhile for Lazy NonEmptyList" + +union :: forall a. Eq a => NonEmptyList a -> NonEmptyList a -> NonEmptyList a +union _ _ = unsafeCrashWith "todo union for Lazy NonEmptyList" + +unionBy :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList a -> NonEmptyList a +unionBy _ _ _ = unsafeCrashWith "todo unionBy for Lazy NonEmptyList" + +unzip :: forall a b. NonEmptyList (Tuple a b) -> Tuple (NonEmptyList a) (NonEmptyList b) +unzip _ = unsafeCrashWith "todo unzip for Lazy NonEmptyList" + +updateAt :: forall a. Int -> a -> NonEmptyList a -> NonEmptyList a +updateAt _ _ _ = unsafeCrashWith "todo updateAt for Lazy NonEmptyList" + +zip :: forall a b. NonEmptyList a -> NonEmptyList b -> NonEmptyList (Tuple a b) +zip _ _ = unsafeCrashWith "todo zip for Lazy NonEmptyList" + +zipWith :: forall a b c. (a -> b -> c) -> NonEmptyList a -> NonEmptyList b -> NonEmptyList c +zipWith _ _ _ = unsafeCrashWith "todo zipWith for Lazy NonEmptyList" + +zipWithA :: forall m a b c. Applicative m => (a -> b -> m c) -> NonEmptyList a -> NonEmptyList b -> m (NonEmptyList c) +zipWithA _ _ _ = unsafeCrashWith "todo zipWithA for Lazy NonEmptyList" + + +insert :: forall a. Ord a => a -> NonEmptyList a -> NonEmptyList a +insert _ _ = unsafeCrashWith "todo insert for Lazy NonEmptyList" +insertBy :: forall a. (a -> a -> Ordering) -> a -> NonEmptyList a -> NonEmptyList a +insertBy _ _ _ = unsafeCrashWith "todo insertBy for Lazy NonEmptyList" +nub :: forall a. Ord a => NonEmptyList a -> NonEmptyList a +nub _ = unsafeCrashWith "todo nub for Lazy NonEmptyList" +nubBy :: forall a. (a -> a -> Ordering) -> NonEmptyList a -> NonEmptyList a +nubBy _ _ = unsafeCrashWith "todo nubBy for Lazy NonEmptyList" +replicate :: forall a. Int -> a -> NonEmptyList a +replicate _ _ = unsafeCrashWith "todo replicate for Lazy NonEmptyList" +replicateM :: forall m a. Monad m => Int -> m a -> m (NonEmptyList a) +replicateM _ _ = unsafeCrashWith "todo replicateM for Lazy NonEmptyList" +some :: forall f a. Alternative f => Lazy (f (NonEmptyList a)) => f a -> f (NonEmptyList a) +some _ = unsafeCrashWith "todo some for Lazy NonEmptyList" +someRec :: forall f a. MonadRec f => Alternative f => f a -> f (NonEmptyList a) +someRec _ = unsafeCrashWith "todo someRec for Lazy NonEmptyList" +sort :: forall a. Ord a => NonEmptyList a -> NonEmptyList a +sort _ = unsafeCrashWith "todo sort for Lazy NonEmptyList" +sortBy :: forall a. (a -> a -> Ordering) -> NonEmptyList a -> NonEmptyList a +sortBy _ _ = unsafeCrashWith "todo sortBy for Lazy NonEmptyList" +transpose :: forall a. NonEmptyList (NonEmptyList a) -> NonEmptyList (NonEmptyList a) +transpose _ = unsafeCrashWith "todo transpose for Lazy NonEmptyList" + +cons' :: forall a. a -> L.List a -> NonEmptyList a +cons' _ _ = unsafeCrashWith "todo cons' for LazyNonEmptyList" +delete :: forall a. Eq a => a -> NonEmptyList a -> L.List a +delete _ _ = unsafeCrashWith "todo delete for LazyNonEmptyList" +deleteBy :: forall a. (a -> a -> Boolean) -> a -> NonEmptyList a -> L.List a +deleteBy _ _ _ = unsafeCrashWith "todo deleteBy for LazyNonEmptyList" +difference :: forall a. Eq a => NonEmptyList a -> NonEmptyList a -> L.List a +difference _ _ = unsafeCrashWith "todo difference for LazyNonEmptyList" +dropEnd :: forall a. Int -> NonEmptyList a -> L.List a +dropEnd _ _ = unsafeCrashWith "todo dropEnd for LazyNonEmptyList" +groupAllBy :: forall a. (a -> a -> Ordering) -> NonEmptyList a -> NonEmptyList (NonEmptyList a) +groupAllBy _ _ = unsafeCrashWith "todo groupAllBy for LazyNonEmptyList" +slice :: Int -> Int -> NonEmptyList ~> L.List +slice _ _ = unsafeCrashWith "todo slice for LazyNonEmptyList" +stripPrefix :: forall a. Eq a => Pattern a -> NonEmptyList a -> Maybe (L.List a) +stripPrefix _ _ = unsafeCrashWith "todo stripPrefix for LazyNonEmptyList" + +deleteAt :: forall a. Int -> NonEmptyList a -> L.List a +deleteAt _ _ = unsafeCrashWith "todo deleteAt for LazyNonEmptyList" + +alterAt :: forall a. Int -> (a -> Maybe a) -> NonEmptyList a -> L.List a +alterAt _ _ _ = unsafeCrashWith "todo alterAt for LazyNonEmptyList" + +cycle :: forall a. NonEmptyList a -> NonEmptyList a +cycle _ = unsafeCrashWith "todo cycle for LazyNonEmptyList" +foldrLazy :: forall a b. Lazy b => (a -> b -> b) -> b -> NonEmptyList a -> b +foldrLazy _ _ _ = unsafeCrashWith "todo foldrLazy for LazyNonEmptyList" +scanlLazy :: forall a b. (b -> a -> b) -> b -> NonEmptyList a -> NonEmptyList b +scanlLazy _ _ _ = unsafeCrashWith "todo scanlLazy for LazyNonEmptyList" + +-- Specialized from Unfoldable1's replicate1 / replicate1A +replicate1 :: forall a. Int -> a -> NonEmptyList a +replicate1 _ _ = unsafeCrashWith "todo replicate1 for LazyNonEmptyList" + +replicate1M :: forall m a. Monad m => Int -> m a -> m (NonEmptyList a) +replicate1M _ _ = unsafeCrashWith "todo replicate1M for LazyNonEmptyList" + +----------- toUnfoldable :: forall f. Unfoldable f => NonEmptyList ~> f toUnfoldable = @@ -75,9 +334,25 @@ uncons (NonEmptyList nel) = case force nel of x :| xs -> { head: x, tail: xs } length :: forall a. NonEmptyList a -> Int length (NonEmptyList nel) = case force nel of _ :| xs -> 1 + L.length xs +-- | Flatten a list of lists. +-- | +-- | Running time: `O(n)`, where `n` is the total number of elements. +concat :: forall a. NonEmptyList (NonEmptyList a) -> NonEmptyList a +concat = (_ >>= identity) + concatMap :: forall a b. (a -> NonEmptyList b) -> NonEmptyList a -> NonEmptyList b concatMap = flip bind appendFoldable :: forall t a. Foldable t => NonEmptyList a -> t a -> NonEmptyList a appendFoldable nel ys = NonEmptyList (defer \_ -> head nel :| tail nel <> L.fromFoldable ys) + +-- | A newtype used in cases where there is a list to be matched. +newtype Pattern a = Pattern (NonEmptyList a) + +derive instance eqPattern :: Eq a => Eq (Pattern a) +derive instance ordPattern :: Ord a => Ord (Pattern a) +derive instance newtypePattern :: Newtype (Pattern a) _ + +instance showPattern :: Show a => Show (Pattern a) where + show (Pattern s) = "(Pattern " <> show s <> ")" diff --git a/src/Data/List/Lazy/Types.purs b/src/Data/List/Lazy/Types.purs index f313a79..b28c30a 100644 --- a/src/Data/List/Lazy/Types.purs +++ b/src/Data/List/Lazy/Types.purs @@ -25,6 +25,7 @@ import Data.TraversableWithIndex (class TraversableWithIndex, traverseWithIndex) import Data.Tuple (Tuple(..), snd) import Data.Unfoldable (class Unfoldable, unfoldr1) import Data.Unfoldable1 (class Unfoldable1) +import Partial.Unsafe (unsafeCrashWith) -- | A lazy linked list. newtype List a = List (Lazy (Step a)) @@ -296,3 +297,6 @@ instance foldableWithIndexNonEmptyList :: FoldableWithIndex Int NonEmptyList whe instance traversableWithIndexNonEmptyList :: TraversableWithIndex Int NonEmptyList where traverseWithIndex f (NonEmptyList ne) = map (\xxs -> NonEmptyList $ defer \_ -> xxs) $ traverseWithIndex (f <<< maybe 0 (add 1)) $ force ne + +instance lazyNonEmptyList :: Z.Lazy (NonEmptyList a) where + defer _ = unsafeCrashWith "todo defer (Lazy instance) for Lazy NonEmptyList" diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 01c3db7..ea4772f 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -5,6 +5,7 @@ module Data.List.NonEmpty , fromList , toList , singleton + , (..), range , length , cons , cons' @@ -36,6 +37,7 @@ module Data.List.NonEmpty , sort , sortBy , take + , takeEnd , takeWhile , drop , dropWhile @@ -60,30 +62,81 @@ module Data.List.NonEmpty , unzip , foldM , module Exports + -- additions + , insert + , insertBy + , Pattern(..) + , some + , someRec + , transpose + + , delete + , deleteBy + , difference + , dropEnd + , slice + , stripPrefix + , deleteAt + , alterAt + ) where import Prelude +import Control.Alternative (class Alternative) +import Control.Lazy (class Lazy) +import Control.Monad.Rec.Class (class MonadRec) import Data.Foldable (class Foldable) +import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports import Data.FunctorWithIndex (mapWithIndex) as FWI import Data.List ((:)) import Data.List as L import Data.List.Types (NonEmptyList(..)) -import Data.Maybe (Maybe(..), fromMaybe, maybe) +import Data.Maybe (Maybe(..), fromJust, fromMaybe, maybe) +import Data.Newtype (class Newtype) import Data.NonEmpty ((:|)) import Data.NonEmpty as NE -import Data.Semigroup.Traversable (sequence1) -import Data.Tuple (Tuple(..), fst, snd) -import Data.Unfoldable (class Unfoldable, unfoldr) -import Partial.Unsafe (unsafeCrashWith) - -import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports import Data.Semigroup.Foldable (fold1, foldMap1, for1_, sequence1_, traverse1_) as Exports +import Data.Semigroup.Traversable (sequence1) import Data.Semigroup.Traversable (sequence1, traverse1, traverse1Default) as Exports import Data.Traversable (scanl, scanr) as Exports - +import Data.Tuple (Tuple(..), fst, snd) +import Data.Unfoldable (class Unfoldable, unfoldr) +import Partial.Unsafe (unsafeCrashWith, unsafePartial) import Prim.TypeError (class Warn, Text) +--- Sorted additions ------ + +insert :: forall a. Ord a => a -> NonEmptyList a -> NonEmptyList a +insert _ _ = unsafeCrashWith "todo insert for NonEmptyList" +insertBy :: forall a. (a -> a -> Ordering) -> a -> NonEmptyList a -> NonEmptyList a +insertBy _ _ _ = unsafeCrashWith "todo insertBy for NonEmptyList" +some :: forall f a. Alternative f => Lazy (f (NonEmptyList a)) => f a -> f (NonEmptyList a) +some _ = unsafeCrashWith "todo some for NonEmptyList" +someRec :: forall f a. MonadRec f => Alternative f => f a -> f (NonEmptyList a) +someRec _ = unsafeCrashWith "todo someRec for NonEmptyList" +transpose :: forall a. NonEmptyList (NonEmptyList a) -> NonEmptyList (NonEmptyList a) +transpose _ = unsafeCrashWith "todo transpose for NonEmptyList" + +delete :: forall a. Eq a => a -> NonEmptyList a -> L.List a +delete _ _ = unsafeCrashWith "todo delete for NonEmptyList" +deleteBy :: forall a. (a -> a -> Boolean) -> a -> NonEmptyList a -> L.List a +deleteBy _ _ _ = unsafeCrashWith "todo deleteBy for NonEmptyList" +difference :: forall a. Eq a => NonEmptyList a -> NonEmptyList a -> L.List a +difference _ _ = unsafeCrashWith "todo difference for NonEmptyList" +dropEnd :: forall a. Int -> NonEmptyList a -> L.List a +dropEnd _ _ = unsafeCrashWith "todo dropEnd for NonEmptyList" +slice :: Int -> Int -> NonEmptyList ~> L.List +slice _ _ = unsafeCrashWith "todo slice for NonEmptyList" +stripPrefix :: forall a. Eq a => Pattern a -> NonEmptyList a -> Maybe (L.List a) +stripPrefix _ _ = unsafeCrashWith "todo stripPrefix for NonEmptyList" + +deleteAt :: forall a. Int -> NonEmptyList a -> Maybe (L.List a) +deleteAt _ _ = unsafeCrashWith "todo deleteAt for NonEmptyList" + +alterAt :: forall a. Int -> (a -> Maybe a) -> NonEmptyList a -> Maybe (L.List a) +alterAt _ _ _ = unsafeCrashWith "todo alterAt for NonEmptyList" + -- | Internal function: any operation on a list that is guaranteed not to delete -- | all elements also applies to a NEL, this function is a helper for defining -- | those cases. @@ -133,6 +186,15 @@ toList (NonEmptyList (x :| xs)) = x : xs singleton :: forall a. a -> NonEmptyList a singleton = NonEmptyList <<< NE.singleton +-- | An infix synonym for `range`. +infix 8 range as .. + +-- | Create a list containing a range of integers, including both endpoints. +range :: Int -> Int -> NonEmptyList Int +range start end | start < end = cons' start (L.range (start + 1) end) + | start > end = cons' start (L.range (start - 1) end) + | otherwise = singleton start + cons :: forall a. a -> NonEmptyList a -> NonEmptyList a cons y (NonEmptyList (x :| xs)) = NonEmptyList (y :| x : xs) @@ -250,6 +312,9 @@ sortBy = wrappedOperation "sortBy" <<< L.sortBy take :: forall a. Int -> NonEmptyList a -> L.List a take = lift <<< L.take +takeEnd :: forall a. Int -> NonEmptyList a -> L.List a +takeEnd = lift <<< L.takeEnd + takeWhile :: forall a. (a -> Boolean) -> NonEmptyList a -> L.List a takeWhile = lift <<< L.takeWhile @@ -274,7 +339,7 @@ group' = groupAll groupBy :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList (NonEmptyList a) groupBy = wrappedOperation "groupBy" <<< L.groupBy -groupAllBy :: forall a. Ord a => (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList (NonEmptyList a) +groupAllBy :: forall a. (a -> a -> Ordering) -> NonEmptyList a -> NonEmptyList (NonEmptyList a) groupAllBy = wrappedOperation "groupAllBy" <<< L.groupAllBy partition :: forall a. (a -> Boolean) -> NonEmptyList a -> { yes :: L.List a, no :: L.List a } @@ -319,3 +384,13 @@ unzip ts = Tuple (map fst ts) (map snd ts) foldM :: forall m a b. Monad m => (b -> a -> m b) -> b -> NonEmptyList a -> m b foldM f b (NonEmptyList (a :| as)) = f b a >>= \b' -> L.foldM f b' as + +-- | A newtype used in cases where there is a list to be matched. +newtype Pattern a = Pattern (NonEmptyList a) + +derive instance eqPattern :: Eq a => Eq (Pattern a) +derive instance ordPattern :: Ord a => Ord (Pattern a) +derive instance newtypePattern :: Newtype (Pattern a) _ + +instance showPattern :: Show a => Show (Pattern a) where + show (Pattern s) = "(Pattern " <> show s <> ")" diff --git a/test/Test/API.purs b/test/Test/API.purs new file mode 100644 index 0000000..fd50cdb --- /dev/null +++ b/test/Test/API.purs @@ -0,0 +1,193 @@ +module Test.API where + +import Prelude + +import Control.Alternative (class Alternative) +import Control.Lazy (class Lazy) +import Control.Monad.Rec.Class (class MonadRec) +import Data.Foldable (class Foldable) +import Data.Maybe (Maybe) +import Data.Tuple (Tuple) +import Data.Unfoldable (class Unfoldable) + +type Common c = + { makeCollection :: forall f a. Foldable f => f a -> c a + + , concat :: forall a. c (c a) -> c a + , concatMap :: forall a. forall b. (a -> c b) -> c a -> c b + , cons :: forall a. a -> c a -> c a + , elemIndex :: forall a. Eq a => a -> c a -> Maybe Int + , elemLastIndex :: forall a. Eq a => a -> c a -> Maybe Int + , findIndex :: forall a. (a -> Boolean) -> c a -> Maybe Int + , findLastIndex :: forall a. (a -> Boolean) -> c a -> Maybe Int + , foldM :: forall m a b. Monad m => (b -> a -> m b) -> b -> c a -> m b + , index :: forall a. c a -> Int -> Maybe a + , intersect :: forall a. Eq a => c a -> c a -> c a + , intersectBy :: forall a. (a -> a -> Boolean) -> c a -> c a -> c a + , length :: forall a. c a -> Int + , nubEq :: forall a. Eq a => c a -> c a + , nubByEq :: forall a. (a -> a -> Boolean) -> c a -> c a + , range :: Int -> Int -> c Int + , reverse :: c ~> c + , singleton :: forall a. a -> c a + , snoc :: forall a. c a -> a -> c a + , toUnfoldable :: forall f a. Unfoldable f => c a -> f a + , union :: forall a. Eq a => c a -> c a -> c a + , unionBy :: forall a. (a -> a -> Boolean) -> c a -> c a -> c a + , unzip :: forall a b. c (Tuple a b) -> Tuple (c a) (c b) + , zip :: forall a b. c a -> c b -> c (Tuple a b) + , zipWith :: forall a b d. (a -> b -> d) -> c a -> c b -> c d + , zipWithA :: forall a b d m. Applicative m => (a -> b -> m d) -> c a -> c b -> m (c d) + + , appendFoldable :: forall t a. Foldable t => c a -> t a -> c a + , insert :: forall a. Ord a => a -> c a -> c a + , insertBy :: forall a. (a -> a -> Ordering) -> a -> c a -> c a + , nub :: forall a. Ord a => c a -> c a + , nubBy :: forall a. (a -> a -> Ordering) -> c a -> c a + -- , replicate :: forall a. Int -> a -> c a + , some :: forall f a. Alternative f => Lazy (f (c a)) => f a -> f (c a) + , someRec :: forall f a. MonadRec f => Alternative f => f a -> f (c a) + , sort :: forall a. Ord a => c a -> c a + , sortBy :: forall a. (a -> a -> Ordering) -> c a -> c a + , transpose :: forall a. c (c a) -> c (c a) + } + +type CommonDiffEmptiability c cInverse canEmpty nonEmpty cPattern = + { makeCollection :: forall f a. Foldable f => f a -> c a + + , makeCanEmptyCollection :: forall f a. Foldable f => f a -> canEmpty a + , makeNonEmptyCollection :: forall f a. Foldable f => f a -> nonEmpty a + , makeInverseCollection :: forall f a. Foldable f => f a -> cInverse a + + , catMaybes :: forall a. c (Maybe a) -> canEmpty a + , drop :: forall a. Int -> c a -> canEmpty a + , dropWhile :: forall a. (a -> Boolean) -> c a -> canEmpty a + , filter :: forall a. (a -> Boolean) -> c a -> canEmpty a + , filterM :: forall m a. Monad m => (a -> m Boolean) -> c a -> m (canEmpty a) + , group :: forall a. Eq a => c a -> c (nonEmpty a) + , groupAll :: forall a. Ord a => c a -> c (nonEmpty a) + , groupBy :: forall a. (a -> a -> Boolean) -> c a -> c (nonEmpty a) + , mapMaybe :: forall a b. (a -> Maybe b) -> c a -> canEmpty b + , partition :: forall a. (a -> Boolean) -> c a -> { no :: canEmpty a, yes :: canEmpty a } + , span :: forall a. (a -> Boolean) -> c a -> { init :: canEmpty a, rest :: canEmpty a } + , take :: forall a. Int -> c a -> canEmpty a + , takeEnd :: forall a. Int -> c a -> canEmpty a + , takeWhile :: forall a. (a -> Boolean) -> c a -> canEmpty a + + , cons' :: forall a. a -> cInverse a -> c a + , delete :: forall a. Eq a => a -> c a -> canEmpty a + , deleteBy :: forall a. (a -> a -> Boolean) -> a -> c a -> canEmpty a + , difference :: forall a. Eq a => c a -> c a -> canEmpty a + , dropEnd :: forall a. Int -> c a -> canEmpty a + , groupAllBy :: forall a. (a -> a -> Ordering) -> c a -> c (nonEmpty a) + , pattern :: forall a. c a -> cPattern a + , slice :: Int -> Int -> c ~> canEmpty + , snoc' :: forall a. cInverse a -> a -> c a + , stripPrefix :: forall a. Eq a => cPattern a -> c a -> Maybe (canEmpty a) +} + +type OnlyCanEmpty c = + { makeCollection :: forall f a. Foldable f => f a -> c a + + -- These functions are not available for non-empty collections + , null :: forall a. c a -> Boolean + , many :: forall f a. Alternative f => Lazy (f (c a)) => f a -> f (c a) + , manyRec :: forall f a. MonadRec f => Alternative f => f a -> f (c a) + + -- These are the same function names as the NonEmpty versions, + -- but the signatures are different and can't be merged in the + -- CommonDiffEmptiability tests. This is due to a mismatch in the + -- presence of `Maybe`s. + , fromFoldable :: forall f. Foldable f => f ~> c + , head :: forall a. c a -> Maybe a + , init :: forall a. c a -> Maybe (c a) + , last :: forall a. c a -> Maybe a + , tail :: forall a. c a -> Maybe (c a) + , uncons :: forall a. c a -> Maybe { head :: a, tail :: c a } + + } + +type OnlyNonEmpty c canEmpty = + { makeCollection :: forall f a. Foldable f => f a -> c a + , makeCanEmptyCollection :: forall f a. Foldable f => f a -> canEmpty a + + -- These functions are only available for NonEmpty collections + , fromList :: forall a. canEmpty a -> Maybe (c a) + , toList :: c ~> canEmpty + + -- These are the same function names as the CanEmpty versions, + -- but the signatures are different and can't be merged in the + -- CommonDiffEmptiability tests. This is due to a mismatch in the + -- presence of `Maybe`s. + , fromFoldable :: forall f a. Foldable f => f a -> Maybe (c a) + , head :: forall a. c a -> a + , init :: forall a. c a -> canEmpty a + , last :: forall a. c a -> a + , tail :: forall a. c a -> canEmpty a + , uncons :: forall a. c a -> { head :: a, tail :: canEmpty a } + } + +type OnlyStrict c = + { makeCollection :: forall f a. Foldable f => f a -> c a + + -- Same names, but different APIs (with Maybe) + , insertAt :: forall a. Int -> a -> c a -> Maybe (c a) + , modifyAt :: forall a. Int -> (a -> a) -> c a -> Maybe (c a) + , updateAt :: forall a. Int -> a -> c a -> Maybe (c a) + } + +type OnlyLazy c = + { makeCollection :: forall f a. Foldable f => f a -> c a + , takeSimple :: forall a. Int -> c a -> c a + + -- Same names, but different APIs (without Maybe) + , insertAt :: forall a. Int -> a -> c a -> c a + , modifyAt :: forall a. Int -> (a -> a) -> c a -> c a + , updateAt :: forall a. Int -> a -> c a -> c a + + -- These are only available for Lazy collections + , repeat :: forall a. a -> c a + , cycle :: forall a. c a -> c a + , iterate :: forall a. (a -> a) -> a -> c a + , foldrLazy :: forall a b. Lazy b => (a -> b -> b) -> b -> c a -> b + , scanlLazy :: forall a b. (b -> a -> b) -> b -> c a -> c b + + -- Specialized from Unfoldable1's replicate1 / replicate1A + , replicate1 :: forall a. Int -> a -> c a + , replicate1M :: forall m a. Monad m => Int -> m a -> m (c a) + } + + +-- Non Overlapping APIs + +type OnlyStrictCanEmpty c = + { + -- Same names, but different APIs + alterAt :: forall a. Int -> (a -> Maybe a) -> c a -> Maybe (c a) + , deleteAt :: forall a. Int -> c a -> Maybe (c a) + } + +type OnlyStrictNonEmpty c canEmpty = + { + -- Same names, but different APIs + alterAt :: forall a. Int -> (a -> Maybe a) -> c a -> Maybe (canEmpty a) + , deleteAt :: forall a. Int -> c a -> Maybe (canEmpty a) + } + +type OnlyLazyCanEmpty c = + { + -- Same names, but different APIs + alterAt :: forall a. Int -> (a -> Maybe a) -> c a -> c a + , deleteAt :: forall a. Int -> c a -> c a + -- Unique functions + -- Specialized from Unfoldable's replicate / replicateA + , replicate :: forall a. Int -> a -> c a + , replicateM :: forall m a. Monad m => Int -> m a -> m (c a) + } + +type OnlyLazyNonEmpty c canEmpty = + { + -- Same names, but different APIs + alterAt :: forall a. Int -> (a -> Maybe a) -> c a -> canEmpty a + , deleteAt :: forall a. Int -> c a -> canEmpty a + } \ No newline at end of file diff --git a/test/Test/AllTests.purs b/test/Test/AllTests.purs new file mode 100644 index 0000000..4aedb8e --- /dev/null +++ b/test/Test/AllTests.purs @@ -0,0 +1,1869 @@ +module Test.AllTests where + +import Prelude + +import Control.Alt (class Alt, (<|>)) +import Control.Alternative (class Alternative, class Plus, empty) +import Control.Comonad (class Comonad) +import Control.Extend (class Extend, (<<=)) +import Control.Lazy (class Lazy, defer) +import Control.MonadPlus (class MonadPlus) +import Control.MonadZero (class MonadZero) +import Data.Array as Array +import Data.Eq (class Eq1, eq1) +import Data.Foldable (class Foldable, foldMap, foldl, foldr, sum, traverse_) +import Data.FoldableWithIndex (class FoldableWithIndex, foldMapWithIndex, foldlWithIndex, foldrWithIndex) +import Data.Function (on) +import Data.FunctorWithIndex (class FunctorWithIndex, mapWithIndex) +import Data.Int (odd) +import Data.List as L +import Data.List.Lazy as LL +import Data.List.Lazy.NonEmpty as LNEL +import Data.List.NonEmpty as NEL +import Data.Maybe (Maybe(..), fromJust) +import Data.Monoid.Additive (Additive(..)) +import Data.Ord (class Ord1, compare1) +import Data.Traversable (class Traversable, sequence, traverse) +import Data.TraversableWithIndex (class TraversableWithIndex, traverseWithIndex) +import Data.Tuple (Tuple(..)) +import Data.Unfoldable (class Unfoldable, unfoldr) +import Data.Unfoldable as Unfoldable +import Data.Unfoldable1 (class Unfoldable1, unfoldr1) +import Data.Unfoldable1 as Unfoldable1 +import Effect (Effect) +import Effect.Console (log) +import Partial.Unsafe (unsafePartial) +import Test.API (Common, CommonDiffEmptiability, OnlyCanEmpty, OnlyLazy, OnlyLazyCanEmpty, OnlyNonEmpty, OnlyStrict, OnlyStrictCanEmpty, OnlyStrictNonEmpty, OnlyLazyNonEmpty) +import Test.Assert (assertEqual, assertEqual', assertFalse, assertTrue) + +{- +Todos + +Improve typeclass law checking +-} + +{- +This "Skip" code is temporarily being used during development. +It allows testing while still patching the API. +This is passed as an additional argument to testCommon +to indicate which collection type is being tested, and +lets us skip gaps that are currently implemented by `unsafeCrashWith`: + +Once fully supported by all collections, can replace with original assert. +-} +data SkipBroken + = SkipBrokenStrictCanEmpty + | SkipBrokenStrictNonEmpty + | SkipBrokenLazyCanEmpty + | SkipBrokenLazyNonEmpty + | RunAll + +derive instance eqSkipBroken :: Eq SkipBroken + +type AssertRec a = { actual :: a , expected :: a } + +assertSkipHelper :: forall a. Eq a => Show a => + SkipBroken -> Array SkipBroken -> (Unit -> AssertRec a) -> Effect Unit +assertSkipHelper skip arr f = + case Array.elem skip arr of + true -> log "...skipped" + false -> assertEqual $ f unit + +assertSkipAlways :: forall a. (Unit -> AssertRec a) -> Effect Unit +assertSkipAlways _ = + log "...skipped" + +printCollectionType :: String -> Effect Unit +printCollectionType str = do + log "--------------------------------" + log str + log "--------------------------------" + +printTestType :: String -> Effect Unit +printTestType str = do + log $ "---- " <> str <> " Tests ----" + +testCommon :: forall c. + Alt c => + Applicative c => + Apply c => + Bind c => + Eq (c Int) => + Eq1 c => + Extend c => + Foldable c => + FoldableWithIndex Int c => + Functor c => + FunctorWithIndex Int c => + Monad c => + Ord (c Int) => + Ord1 c => + Semigroup (c Int) => + Show (c Int) => + Traversable c => + TraversableWithIndex Int c => + Unfoldable1 c => + -- The below contraints are for unit testing asserts: + Eq (c String) => + Eq (c (Tuple Int String)) => + Eq (c (c String)) => + Eq (c (c Int)) => + Eq (c (Array Int)) => + Show (c String) => + Show (c (Tuple Int String)) => + Show (c (c String)) => + Show (c (c Int)) => + Show (c (Array Int)) => + -- parameters: + Common c -> + Effect Unit +testCommon + { makeCollection + + , appendFoldable + , concat + , concatMap + , cons + , elemIndex + , elemLastIndex + , findIndex + , findLastIndex + , foldM + , index + , insert + , insertBy + , intersect + , intersectBy + , length + , nub + , nubBy + , nubEq + , nubByEq + , range + , reverse + , singleton + , snoc + , some + , someRec + , sort + , sortBy + , toUnfoldable + , transpose + , union + , unionBy + , unzip + , zip + , zipWith + , zipWithA + + } = do + let + l = makeCollection + + rg :: Int -> Int -> c Int + rg = range + + bigCollection :: c _ + bigCollection = range 1 100000 + + printTestType "Common" + + -- Testing range asap, since many other tests rely on it. + log "range should create an inclusive collection of integers for the specified start and end" + assertEqual { actual: range 3 3, expected: l [3] } + assertEqual { actual: range 0 5, expected: l [0, 1, 2, 3, 4, 5] } + assertEqual { actual: range 2 (-3), expected: l [2, 1, 0, -1, -2, -3] } + + -- ======= Typeclass tests ======== + + -- Alt + -- alt :: forall a. f a -> f a -> f a + + log "Alt's alt (<|>) should append collections" + assertEqual { actual: l [1,2] <|> l [3,4], expected: l [1,2,3,4] } + + -- Applicative + -- pure :: forall a. a -> f a + + log "Applicative's pure should construct a collection with a single value" + assertEqual { actual: pure 5, expected: l [5] } + + -- Apply + -- apply :: forall a b. f (a -> b) -> f a -> f b + + -- Todo - Fix ordering mismatch between list types. Also ensure ordering is the same for arrays. + + log "Apply's apply (<*>) should have cartesian product behavior for non-zippy collections" + assertEqual { actual: l [mul 10, mul 100] <*> l [1, 2, 3], expected: l [10, 20, 30, 100, 200, 300] } + + -- Bind c + -- bind :: forall a b. m a -> (a -> m b) -> m b + + log "Bind's bind (>>=) should append the results of a collection-generating function\ + \applied to each element in the collection" + assertEqual { actual: l [1,2,3] >>= \x -> l [x,10+x], expected: l [1,11,2,12,3,13] } + + -- Eq + -- eq :: a -> a -> Boolean + + log "Eq's eq (==) should correctly test collections for equality" + assertEqual' "Equality failed" { actual: l [1,2] == l [1,2], expected: true } + assertEqual' "Inequality failed" { actual: l [1,2] == l [2,2], expected: false } + + -- Eq1 + -- eq1 :: forall a. Eq a => f a -> f a -> Boolean + + log "Eq1's eq1 should correctly test collections for equality" + assertEqual' "Equality failed" { actual: l [1,2] `eq1` l [1,2], expected: true } + assertEqual' "Inequality failed" { actual: l [1,2] `eq1` l [2,2], expected: false } + + -- Extend + -- extend :: forall b a. (w a -> b) -> w a -> w b + + log "Extend's extend (<<=) should create a collection containing the results\ + \of a function that is applied to increasingly smaller chunks of an input\ + \collection. Each iteration drops an element from the front of the input collection." + assertEqual { actual: sum <<= l [1,2,3,4], expected: l [10,9,7,4] } + + -- Foldable + -- foldl :: forall a b. (b -> a -> b) -> b -> f a -> b + -- foldr :: forall a b. (a -> b -> b) -> b -> f a -> b + -- foldMap :: forall a m. Monoid m => (a -> m) -> f a -> m + + log "Foldable's foldl should correctly fold left-to-right" + assertEqual { actual: foldl (\b a -> b * 10 + a) 0 $ rg 1 5, expected: 12345 } + + log "Foldable's foldr should correctly fold right-to-left" + assertEqual { actual: foldr (\a b -> b * 10 + a) 0 $ rg 1 5, expected: 54321 } + + log "Foldable's foldMap should be left-to-right" + assertEqual { actual: foldMap show $ rg 1 5, expected: "12345" } + + log "Foldable's foldl should be stack-safe" + void $ pure $ foldl (+) 0 bigCollection + + log "Foldable's foldr should be stack-safe" + void $ pure $ foldr (+) 0 bigCollection + + log "Foldable's foldMap should be stack-safe" + void $ pure $ foldMap Additive bigCollection + + -- FoldableWithIndex + -- foldlWithIndex :: forall a b. (i -> b -> a -> b) -> b -> f a -> b + -- foldrWithIndex :: forall a b. (i -> a -> b -> b) -> b -> f a -> b + -- foldMapWithIndex :: forall a m. Monoid m => (i -> a -> m) -> f a -> m + + log "FoldableWithIndex's foldlWithIndex should correctly fold left-to-right" + assertEqual { actual: foldlWithIndex (\_ b a -> b * 10 + a) 0 $ rg 1 5, expected: 12345 } + + log "FoldableWithIndex's foldrWithIndex should correctly fold right-to-left" + assertEqual { actual: foldrWithIndex (\_ a b -> b * 10 + a) 0 $ rg 1 5, expected: 54321 } + + log "FoldableWithIndex's foldMapWithIndex should be left-to-right" + assertEqual { actual: foldMapWithIndex (\_ a -> show a) $ rg 1 5, expected: "12345" } + + + log "FoldableWithIndex's foldlWithIndex should increment indices" + assertEqual { actual: foldlWithIndex (\i b _ -> b * 10 + i) 0 $ l [0, 0, 0, 0, 0], expected: 1234 } + + log "FoldableWithIndex's foldrWithIndex should decrement indices" + assertEqual { actual: foldrWithIndex (\i _ b -> b * 10 + i) 0 $ l [0, 0, 0, 0, 0], expected: 43210 } + + log "FoldableWithIndex's foldMapWithIndex should increment indices" + assertEqual { actual: foldMapWithIndex (\i _ -> show i) $ l [0, 0, 0, 0, 0], expected: "01234" } + + + log "FoldableWithIndex's foldlWithIndex should be stack-safe" + void $ pure $ foldlWithIndex (\i b _ -> i + b) 0 bigCollection + + log "FoldableWithIndex's foldrWithIndex should be stack-safe" + void $ pure $ foldrWithIndex (\i _ b -> i + b) 0 bigCollection + + log "FoldableWithIndex's foldMapWithIndex should be stack-safe" + void $ pure $ foldMapWithIndex (\i _ -> Additive i) bigCollection + + -- Functor + -- map :: forall a b. (a -> b) -> f a -> f b + + log "Functor's map should be correct" + assertEqual { actual: map (add 1) $ rg 0 4, expected: rg 1 5 } + + log "Functor's map should be stack-safe" + void $ pure $ map identity bigCollection + + -- FunctorWithIndex + -- mapWithIndex :: forall a b. (i -> a -> b) -> f a -> f b + + log "FunctorWithIndex's mapWithIndex should take a collection of values and apply a function which also takes the index into account" + assertEqual { actual: mapWithIndex add $ l [10, 10, 10, 10, 10], expected: l [10, 11, 12, 13, 14] } + + log "FunctorWithIndex's mapWithIndex should be stack-safe" + void $ pure $ mapWithIndex add bigCollection + + -- Monad + -- Indicates Applicative and Bind, which are already tested above + + -- Ord + -- compare :: a -> a -> Ordering + + log "Ord's compare should determine the ordering of two collections" + assertEqual { actual: compare (l [1]) (l [1]), expected: EQ } + assertEqual { actual: compare (l [0]) (l [1]), expected: LT } + assertEqual { actual: compare (l [2]) (l [1]), expected: GT } + assertEqual { actual: compare (l [1]) (l [1, 1]), expected: LT } + assertEqual { actual: compare (l [1, 1]) (l [1]), expected: GT } + assertEqual { actual: compare (l [1, 1]) (l [1, 2]), expected: LT } + assertEqual { actual: compare (l [1, 2]) (l [1, 1]), expected: GT } + + -- Ord1 + -- compare1 :: forall a. Ord a => f a -> f a -> Ordering + + log "Ord1's compare1 should determine the ordering of two collections" + assertEqual { actual: compare1 (l [1]) (l [1]), expected: EQ } + assertEqual { actual: compare1 (l [0]) (l [1]), expected: LT } + assertEqual { actual: compare1 (l [2]) (l [1]), expected: GT } + assertEqual { actual: compare1 (l [1]) (l [1, 1]), expected: LT } + assertEqual { actual: compare1 (l [1, 1]) (l [1]), expected: GT } + assertEqual { actual: compare1 (l [1, 1]) (l [1, 2]), expected: LT } + assertEqual { actual: compare1 (l [1, 2]) (l [1, 1]), expected: GT } + + -- Semigroup + -- append :: a -> a -> a + + log "Semigroup's append (<>) should concatenate two collections" + assertEqual { actual: l [1, 2] <> l [3, 4], expected: l [1, 2, 3, 4] } + + log "Semigroup's append (<>) should be stack-safe" + void $ pure $ bigCollection <> bigCollection + + -- Show + -- show :: a -> String + -- This is not testable in a generic way + + -- Traversable + -- traverse :: forall a b m. Applicative m => (a -> m b) -> t a -> m (t b) + -- sequence :: forall a m. Applicative m => t (m a) -> m (t a) + + let + safeDiv :: Int -> Int -> Maybe Int + safeDiv _ 0 = Nothing + safeDiv n d = Just $ n / d + + log "Traversable's traverse should be correct" + assertEqual { actual: traverse (safeDiv 12) $ l [1, 2, 3, 4], expected: Just $ l [12, 6, 4, 3] } + assertEqual { actual: traverse (safeDiv 12) $ l [1, 2, 0, 4], expected: Nothing } + + log "Traversable's sequence should be correct" + assertEqual { actual: sequence $ l [Just 1, Just 2, Just 3], expected: Just $ l [1, 2, 3] } + assertEqual { actual: sequence $ l [Just 1, Nothing, Just 3], expected: Nothing } + + log "Traversable's traverse should be stack-safe" + assertEqual { actual: traverse Just bigCollection, expected: Just bigCollection } + + log "Traversable's sequence should be stack-safe" + assertEqual { actual: sequence $ map Just bigCollection, expected: Just bigCollection } + + -- TraversableWithIndex + -- traverseWithIndex :: forall a b m. Applicative m => (i -> a -> m b) -> t a -> m (t b) + + log "TraversableWithIndex's traverseWithIndex should be correct" + assertEqual { actual: traverseWithIndex safeDiv $ l [2, 2, 2, 2], expected: Just $ l [0, 0, 1, 1] } + assertEqual { actual: traverseWithIndex safeDiv $ l [2, 2, 0, 2], expected: Nothing } + + log "TraversableWithIndex's traverseWithIndex should be stack-safe" + assertEqual { actual: traverseWithIndex (const Just) bigCollection, expected: Just bigCollection } + + -- Unfoldable1 + -- unfoldr1 :: forall a b. (b -> Tuple a (Maybe b)) -> b -> t a + + let + step1 :: Int -> Tuple Int (Maybe Int) + step1 n = Tuple n $ if n >= 5 then Nothing else Just $ n + 1 + + log "Unfoldable1's unfoldr1 should maintain order" + assertEqual { actual: unfoldr1 step1 1, expected: rg 1 5 } + + log "Unfoldable1's replicate1 should be correct" + assertEqual { actual: Unfoldable1.replicate1 3 1, expected: l [1, 1, 1] } + assertEqual { actual: Unfoldable1.replicate1 1 1, expected: l [1] } + assertEqual { actual: Unfoldable1.replicate1 0 1, expected: l [1] } + assertEqual { actual: Unfoldable1.replicate1 (-1) 1, expected: l [1] } + + log "Unfoldable1's replicate1 should be stack-safe" + assertEqual { actual: length (Unfoldable1.replicate1 100000 1), expected: 100000 } + + -- =========== Functions =========== + + log "appendFoldable should append a foldable collection to another collection" + assertEqual { actual: appendFoldable (l [1, 2, 3]) [4, 5], expected: l [1, 2, 3, 4, 5] } + assertEqual { actual: appendFoldable (l [1, 2, 3]) [], expected: l [1, 2, 3] } + + log "concat should join a collection of collections" + assertEqual { actual: concat (l [l [1, 2], l [3, 4]]), expected: l [1, 2, 3, 4] } + assertEqual { actual: concat (l [l [1, 2]]), expected: l [1, 2] } + + let + doubleAndOrig :: Int -> c Int + doubleAndOrig x = cons (x * 2) $ singleton x + + log "concatMap should be equivalent to (concat <<< map)" + assertEqual { actual: concatMap doubleAndOrig $ l [1, 2, 3], expected: concat (map doubleAndOrig $ l [1, 2, 3]) } + assertEqual { actual: concatMap doubleAndOrig $ l [1, 2, 3], expected: l [2, 1, 4, 2, 6, 3] } + + log "cons should add an element to the front of the collection" + assertEqual { actual: cons 1 $ l [2, 3], expected: l [1,2,3] } + + log "elemIndex should return the index of an item that a predicate returns true for in a collection" + assertEqual { actual: elemIndex 1 $ l [1, 2, 1], expected: Just 0 } + assertEqual { actual: elemIndex 4 $ l [1, 2, 1], expected: Nothing } + assertEqual { actual: elemIndex (-1) $ l [1, 2, 1], expected: Nothing } + + log "elemLastIndex should return the last index of an item in a collection" + assertEqual { actual: elemLastIndex 1 $ l [1, 2, 1], expected: Just 2 } + assertEqual { actual: elemLastIndex 4 $ l [1, 2, 1], expected: Nothing } + assertEqual { actual: elemLastIndex (-1) $ l [1, 2, 1], expected: Nothing } + + log "findIndex should return the index of an item that a predicate returns true for in a collection" + assertEqual { actual: findIndex (_ /= 1) $ l [1, 2, 1], expected: Just 1 } + assertEqual { actual: findIndex (_ == 3) $ l [1, 2, 1], expected: Nothing } + + log "findLastIndex should return the last index of an item in a collection" + assertEqual { actual: findLastIndex (_ /= 1) $ l [2, 1, 2], expected: Just 2 } + assertEqual { actual: findLastIndex (_ == 3) $ l [2, 1, 2], expected: Nothing } + + log "foldM should perform a fold using a monadic step function" + let + foldMFunc _ 0 = Nothing + foldMFunc x y = Just $ x + y + assertEqual { actual: foldM foldMFunc 0 $ l [1, 2, 3, 4], expected: Just 10 } + assertEqual { actual: foldM foldMFunc 0 $ l [1, 2, 0, 4], expected: Nothing } + + log "index (!!) should return Just x when the index is within the bounds of the collection" + assertEqual { actual: l [1, 2, 3] `index` 0, expected: Just 1 } + assertEqual { actual: l [1, 2, 3] `index` 1, expected: Just 2 } + assertEqual { actual: l [1, 2, 3] `index` 2, expected: Just 3 } + + log "index (!!) should return Nothing when the index is outside of the bounds of the collection" + assertEqual { actual: l [1, 2, 3] `index` 6, expected: Nothing } + assertEqual { actual: l [1, 2, 3] `index` (-1), expected: Nothing } + + log "insert should add an item at the appropriate place in a sorted collection" + assertEqual { actual: insert 2 $ l [1, 1, 3], expected: l [1, 1, 2, 3] } + assertEqual { actual: insert 4 $ l [1, 2, 3], expected: l [1, 2, 3, 4] } + assertEqual { actual: insert 0 $ l [1, 2, 3], expected: l [0, 1, 2, 3] } + + log "insertBy should add an item at the appropriate place in a sorted collection using the specified comparison" + assertEqual { actual: insertBy (flip compare) 4 $ l [1, 2, 3], expected: l [4, 1, 2, 3] } + assertEqual { actual: insertBy (flip compare) 0 $ l [1, 2, 3], expected: l [1, 2, 3, 0] } + + log "intersect should return the intersection of two collections" + assertEqual { actual: intersect (l [1, 2, 3, 4, 3, 2, 1]) $ l [1, 1, 2, 3], expected: l [1, 2, 3, 3, 2, 1] } + + log "intersectBy should return the intersection of two collections using the specified equivalence relation" + assertEqual { actual: intersectBy (\x y -> x * 2 == y) (l [1, 2, 3]) $ l [2, 6], expected: l [1, 3] } + + log "length should return the number of items in a collection" + assertEqual { actual: length (l [1]), expected: 1 } + assertEqual { actual: length (l [1, 2, 3, 4, 5]), expected: 5 } + + log "length should be stack-safe" + void $ pure $ length bigCollection + + log "nub should remove duplicate elements from a collection, keeping the first occurrence" + assertEqual { actual: nub (l [1, 2, 2, 3, 4, 1]), expected: l [1, 2, 3, 4] } + + log "nubBy should remove duplicate items from a collection using a supplied predicate" + assertEqual { actual: nubBy (compare `on` Array.length) $ l [[1],[2],[3,4]], expected: l [[1],[3,4]] } + + log "nubEq should remove duplicate elements from the collection, keeping the first occurence" + assertEqual { actual: nubEq (l [1, 2, 2, 3, 4, 1]), expected: l [1, 2, 3, 4] } + + log "nubByEq should remove duplicate items from the collection using a supplied predicate" + let mod3eq = eq `on` \n -> mod n 3 + assertEqual { actual: nubByEq mod3eq $ l [1, 3, 4, 5, 6], expected: l [1, 3, 5] } + + log "reverse should reverse the order of items in a collection" + assertEqual { actual: reverse (l [1, 2, 3]), expected: l [3, 2, 1] } + + log "singleton should construct a collection with a single value" + assertEqual { actual: singleton 5, expected: l [5] } + + log "snoc should add an item to the end of a collection" + assertEqual { actual: l [1, 2, 3] `snoc` 4, expected: l [1, 2, 3, 4] } + + -- Todo - create tests for these functions + + -- some :: forall f a. Alternative f => Lazy (f (c a)) => f a -> f (c a) + -- someRec :: forall f a. MonadRec f => Alternative f => f a -> f (c a) + + log "sort should reorder a collection into ascending order based on the result of compare" + assertEqual { actual: sort (l [1, 3, 2, 5, 6, 4]), expected: l [1, 2, 3, 4, 5, 6] } + + log "sortBy should reorder a collection into ascending order based on the result of a comparison function" + assertEqual { actual: sortBy (flip compare) $ l [1, 3, 2, 5, 6, 4] + , expected: l [6, 5, 4, 3, 2, 1] } + + log "toUnfoldable should convert to any unfoldable collection" + traverse_ (\xs -> assertEqual { actual: toUnfoldable (l xs), expected: xs }) + [ [1] + , [1,2,3] + , [4,0,0,1,25,36,458,5842,23757] + ] + + log "transpose should swap 'rows' and 'columns' of a collection of collections" + assertEqual { actual: transpose (l [l [1,2,3], l[4,5,6], l [7,8,9]]) + , expected: l [l [1,4,7], l[2,5,8], l [3,6,9]] } + log "transpose should skip elements when row lengths don't match" + assertEqual { actual: transpose (l [l [10, 11], l [20], l [30, 31, 32]]) + , expected: l [l [10, 20, 30], l [11, 31], l [32]] } + + log "union should produce the union of two collections" + assertEqual { actual: union (l [1, 2, 3]) $ l [2, 3, 4], expected: l [1, 2, 3, 4] } + assertEqual { actual: union (l [0, 0]) $ l [1, 1], expected: l [0, 0, 1] } + + log "unionBy should produce the union of two collections using the specified equality relation" + assertEqual { actual: unionBy mod3eq (l [1, 5, 1, 2]) $ l [3, 4, 3, 3], expected: l [1, 5, 1, 2, 3] } + + log "unzip should deconstruct a collection of tuples into a tuple of collections" + assertEqual { actual: unzip (l [Tuple 1 "a", Tuple 2 "b", Tuple 3 "c"]), expected: Tuple (l [1, 2, 3]) $ l ["a", "b", "c"] } + + log "zip should use the specified function to zip two collections together" + assertEqual { actual: zip (l [1, 2, 3]) $ l ["a", "b", "c"], expected: l [Tuple 1 "a", Tuple 2 "b", Tuple 3 "c"] } + assertEqual { actual: zip (l [1, 2, 3]) $ l ["a", "b"], expected: l [Tuple 1 "a", Tuple 2 "b"] } + assertEqual { actual: zip (l [1, 2]) $ l ["a", "b", "c"], expected: l [Tuple 1 "a", Tuple 2 "b"] } + + log "zipWith should use the specified function to zip two collections together" + assertEqual { actual: zipWith (\x y -> l [show x, y]) (l [1, 2, 3]) $ l ["a", "b", "c"], expected: l [l ["1", "a"], l ["2", "b"], l ["3", "c"]] } + assertEqual { actual: zipWith (\x y -> l [show x, y]) (l [1, 2, 3]) $ l ["a", "b"], expected: l [l ["1", "a"], l ["2", "b"]] } + assertEqual { actual: zipWith (\x y -> l [show x, y]) (l [1, 2]) $ l ["a", "b", "c"], expected: l [l ["1", "a"], l ["2", "b"]] } + + log "zipWithA should use the specified function to zip two collections together" + let + zipWithAFunc 0 _ = Nothing + zipWithAFunc x y = Just $ Tuple x y + assertEqual { actual: zipWithA zipWithAFunc (l [1, 2, 3]) $ l ["a", "b", "c"], expected: Just $ l [Tuple 1 "a", Tuple 2 "b", Tuple 3 "c"] } + assertEqual { actual: zipWithA zipWithAFunc (l [1, 2, 0]) $ l ["a", "b", "c"], expected: Nothing } + assertEqual { actual: zipWithA zipWithAFunc (l [1, 2, 3]) $ l ["a", "b"], expected: Just $ l [Tuple 1 "a", Tuple 2 "b"] } + assertEqual { actual: zipWithA zipWithAFunc (l [1, 2, 0]) $ l ["a", "b"], expected: Just $ l [Tuple 1 "a", Tuple 2 "b"] } + assertEqual { actual: zipWithA zipWithAFunc (l [1, 2]) $ l ["a", "b", "c"], expected: Just $ l [Tuple 1 "a", Tuple 2 "b"] } + assertEqual { actual: zipWithA zipWithAFunc (l [0, 2]) $ l ["a", "b", "c"], expected: Nothing } + + +testCommonDiffEmptiability :: forall c cInverse canEmpty nonEmpty cPattern. + Eq (c (nonEmpty Int)) => + Eq (canEmpty Int) => + Eq (c (c Int)) => + Eq (c Int) => + Show (c (nonEmpty Int)) => + Show (canEmpty Int) => + Show (c (c Int)) => + Show (c Int) => + -- parameters: + SkipBroken -> + CommonDiffEmptiability c cInverse canEmpty nonEmpty cPattern -> + Effect Unit +testCommonDiffEmptiability skip + { makeCollection + , makeCanEmptyCollection + , makeNonEmptyCollection + , makeInverseCollection + + , catMaybes + , cons' + , delete + , deleteBy + , difference + , dropEnd + , drop + , dropWhile + , filter + , filterM + , group + , groupAll + , groupAllBy + , groupBy + , mapMaybe + , partition + , pattern + , slice + , snoc' + , span + , stripPrefix + , take + , takeEnd + , takeWhile + + } = do + let + l = makeCollection + cel = makeCanEmptyCollection + nel = makeNonEmptyCollection + ivl = makeInverseCollection + + assertSkip :: forall a. Eq a => Show a => Array SkipBroken -> (_ -> AssertRec a) -> Effect Unit + assertSkip = assertSkipHelper skip + + printTestType "Common (where signatures differ based on emptiability)" + + log "catMaybes should take a collection of Maybe values and remove the Nothings" + assertEqual { actual: catMaybes (l [Nothing, Just 2, Nothing, Just 4]), expected: cel [2, 4] } + + log "cons' should create a collection by prepending an element to an 'inverse' (can-empty or not) collection" + assertEqual { actual: cons' 1 $ ivl [2, 3], expected: l [1, 2, 3] } + + log "delete should remove the first matching item from a collection" + assertEqual { actual: delete 1 $ l [1, 2, 1], expected: cel [2, 1] } + assertEqual { actual: delete 2 $ l [1, 2, 1], expected: cel [1, 1] } + assertEqual { actual: delete 3 $ l [1, 2, 1], expected: cel [1, 2, 1] } + assertEqual { actual: delete 2 $ l [2], expected: cel [] } + + log "deleteBy should remove the first equality-relation-matching item from a collection" + assertEqual { actual: deleteBy (/=) 2 $ l [1, 2, 1], expected: cel [2, 1] } + assertEqual { actual: deleteBy (/=) 1 $ l [1, 2, 1], expected: cel [1, 1] } + assertEqual { actual: deleteBy (/=) 1 $ l [1, 1, 1], expected: cel [1, 1, 1] } + assertEqual { actual: deleteBy (/=) 1 $ l [2], expected: cel [] } + + log "difference (\\\\) should return the 'difference' between two collections" + assertEqual { actual: l [1, 2, 3, 4, 3, 2] `difference` l [1], expected: cel [2,3,4,3,2] } + assertEqual { actual: l [1, 2, 3, 4, 3, 2] `difference` l [2], expected: cel [1,3,4,3,2] } + assertEqual { actual: l [1, 2, 3, 4, 3, 2] `difference` l [2, 2], expected: cel [1,3,4,3] } + assertEqual { actual: l [1, 2, 3, 4, 3, 2] `difference` l [2, 2, 2], expected: cel [1,3,4,3] } + assertEqual { actual: l [1, 2, 3] `difference` l [3, 1, 2], expected: cel [] } + + log "drop should remove the specified number of items from the front of a collection" + assertEqual { actual: drop 1 $ l [1, 2, 3], expected: cel [2, 3] } + assertEqual { actual: drop (-1) $ l [1, 2, 3], expected: cel [1, 2, 3] } + + log "dropEnd should remove the specified number of items from the end of a collection" + assertEqual { actual: dropEnd (-1) $ l [1, 2, 3], expected: cel [1, 2, 3] } + assertEqual { actual: dropEnd 0 $ l [1, 2, 3], expected: cel [1, 2, 3] } + assertEqual { actual: dropEnd 1 $ l [1, 2, 3], expected: cel [1, 2] } + assertEqual { actual: dropEnd 2 $ l [1, 2, 3], expected: cel [1] } + assertEqual { actual: dropEnd 3 $ l [1, 2, 3], expected: cel [] } + assertEqual { actual: dropEnd 4 $ l [1, 2, 3], expected: cel [] } + + log "dropWhile should remove all values that match a predicate from the front of a collection" + assertEqual { actual: dropWhile (_ /= 1) $ l [1, 2, 3], expected: cel [1, 2, 3] } + assertEqual { actual: dropWhile (_ /= 2) $ l [1, 2, 3], expected: cel [2, 3] } + + -- Surprised this does not work with $ + -- let l10 = l $ Array.range 0 10 + let l10 = l (Array.range 0 10) + -- More discussion here: + -- https://discourse.purescript.org/t/apply-is-not-always-a-valid-substitute-for-parens/2301 + + log "filter should remove items that don't match a predicate" + assertEqual { actual: filter odd l10, expected: cel [1, 3, 5, 7, 9] } + + log "filterM should remove items that don't match a predicate while using a monadic behaviour" + assertEqual { actual: filterM (Just <<< odd) l10, expected: Just $ cel [1, 3, 5, 7, 9] } + assertEqual { actual: filterM (const Nothing) l10, expected: Nothing } + + log "group should group consecutive equal elements into collections" + assertEqual { actual: group (l [1, 2, 2, 3, 3, 3, 1]) + , expected: l [nel [1], nel [2, 2], nel [3, 3, 3], nel [1]] } + + log "groupAll should group equal elements into collections" + assertSkip [SkipBrokenLazyCanEmpty] + \_ -> { actual: groupAll (l [1, 2, 2, 3, 3, 3, 1]), expected: l [nel [1, 1], nel [2, 2], nel [3, 3, 3]] } + + log "groupAllBy should sort then group equal elements into lists based on a comparison function" + assertEqual { actual: groupAllBy (compare `on` (_ `div` 10)) $ l [32, 31, 21, 22, 11, 33] + , expected: l [nel [11], nel [21, 22], nel [32, 31, 33]] } + + log "groupBy should group consecutive equal elements into collections based on an equivalence relation" + assertEqual { actual: groupBy (eq `on` (_ `mod` 10)) $ l [1, 2, 12, 3, 13, 23, 11] + , expected: l [nel [1], nel [2, 12], nel [3, 13, 23], nel [11]] } + + log "mapMaybe should transform every item in a collection, throwing out Nothing values" + assertEqual { actual: mapMaybe (\x -> if x /= 0 then Just x else Nothing) $ l [0, 1, 0, 0, 2, 3] + , expected: cel [1, 2, 3] } + + log "partition should separate a collection into a tuple of collections that do and do not satisfy a predicate" + assertEqual { actual: partition (_ > 2) $ l [1, 5, 3, 2, 4] + , expected: { yes: cel [5, 3, 4], no: cel [1, 2] } } + + + log "slice should extract a sub-collection by an inclusive start and exclusive end index" + let nums = l [0, 1, 2, 3, 4] + assertEqual { actual: slice 1 3 nums, expected: cel [1, 2] } + + log "slice should make best effort for out-of-bounds (but intersecting) indices" + assertEqual { actual: slice 3 7 nums, expected: cel [3, 4] } + -- assertEqual { actual: slice (-2) 3 nums, expected: cel [1, 2] } -- Todo - broken, returns full collection + + log "slice should return an empty collection if indices do not intersect with available elements" + assertEqual { actual: slice 5 7 nums, expected: cel [] } + + log "slice should return an empty collection if indices are not incrementing" + assertEqual { actual: slice 3 1 nums, expected: cel [] } + + + log "snoc' should create a collection by appending an element to an 'inverse' (can-empty or not) collection" + assertEqual { actual: snoc' (ivl [1, 2]) 3, expected: l [1, 2, 3] } + + log "span should split a collection in two based on a predicate" + assertEqual { actual: span (_ < 4) $ l [1, 2, 3, 4, 5, 6, 1] + , expected: { init: cel [1, 2, 3], rest: cel [4, 5, 6, 1] } } + assertEqual { actual: span (_ < 4) $ l [9, 2, 3, 4, 5, 6, 1] + , expected: { init: cel [], rest: cel [9, 2, 3, 4, 5, 6, 1] } } + + log "stripPrefix should remove elements matching a pattern from the start of a collection" + assertEqual { actual: stripPrefix (pattern (l [4, 2])) $ l [4, 2, 5, 1] , expected: Just $ cel [5, 1] } + assertEqual { actual: stripPrefix (pattern (l [4, 2])) $ l [4, 2] , expected: Just $ cel [] } + + log "stripPrefix should return nothing if starting elements do not match pattern" + assertEqual { actual: stripPrefix (pattern (l [4, 2])) $ l [4, 4, 2, 5, 1] , expected: Nothing } + assertEqual { actual: stripPrefix (pattern (l [4, 2])) $ l [4] , expected: Nothing } + + log "take should keep the specified number of items from the front of a collection, discarding the rest" + assertEqual { actual: take 1 $ l [1, 2, 3], expected: cel [1] } + assertEqual { actual: take 2 $ l [1, 2, 3], expected: cel [1, 2] } + assertEqual { actual: take 0 $ l [1, 2], expected: cel [] } + assertEqual { actual: take (-1) $ l [1, 2], expected: cel [] } + + log "takeEnd should keep the specified number of items from the end of a collection, discarding the rest" + assertSkip [SkipBrokenLazyCanEmpty] + \_ -> { actual: takeEnd 1 $ l [1, 2, 3], expected: cel [3] } + assertSkip [SkipBrokenLazyCanEmpty] + \_ -> { actual: takeEnd 2 $ l [1, 2, 3], expected: cel [2, 3] } + assertSkip [SkipBrokenLazyCanEmpty] + \_ -> { actual: takeEnd 2 $ l [1], expected: cel [1] } + + log "takeWhile should keep all values that match a predicate from the front of a collection" + assertEqual { actual: takeWhile (_ /= 2) $ l [1, 2, 3], expected: cel [1] } + assertEqual { actual: takeWhile (_ /= 3) $ l [1, 2, 3], expected: cel [1, 2] } + + +testOnlyCanEmpty :: forall c nonEmpty cPattern. + -- OnlyCanEmpty API + Alternative c => + MonadPlus c => + MonadZero c => + Monoid (c Int) => + Plus c => + Unfoldable c => + -- Common API with additional canEmpty tests + Alt c => + Apply c => + Bind c => + Extend c => + Foldable c => + FoldableWithIndex Int c => + Functor c => + FunctorWithIndex Int c => + Monad c => + Ord (c Int) => + Ord1 c => + Semigroup (c Int) => + Traversable c => + TraversableWithIndex Int c => + -- Constraints for unit test asserts + Eq (c Int) => + Eq (c (nonEmpty Int)) => + Eq (c (c Int)) => + Eq (c (c String)) => + Eq (c (Array Int)) => + Eq (c (Tuple Int String)) => + Show (c Int) => + Show (c (nonEmpty Int)) => + Show (c (c Int)) => + Show (c (c String)) => + Show (c (Array Int)) => + Show (c (Tuple Int String)) => + -- parameters: + SkipBroken -> + OnlyCanEmpty c -> + Common c -> + CommonDiffEmptiability c nonEmpty c nonEmpty cPattern -> + Effect Unit +testOnlyCanEmpty skip + -- OnlyCanEmpty + { makeCollection + + -- Only available for CanEmpty collections + , null + , many + , manyRec + + -- Todo - can these be deduplicated into diff-empty using Maybe / identity constructor? + + -- Can't be deduplicated from NonEmpty collections due to use of Maybe + , fromFoldable + , head + , init + , last + , tail + , uncons + } + + -- Test these common functions again with empty collections: + + -- Common + { appendFoldable + , concat + , concatMap + , cons + , elemIndex + , elemLastIndex + , findIndex + , findLastIndex + , foldM + , index + , insert + , insertBy + , intersect + , intersectBy + , length + , nub + , nubBy + , nubEq + , nubByEq + , reverse + , singleton + , snoc + , some + , someRec + , sort + , sortBy + , toUnfoldable + , transpose + , union + , unionBy + , unzip + , zip + , zipWith + , zipWithA + } + + -- CommonDiffEmptiability + { catMaybes + -- , cons' -- tested in onlyNonEmpty + , delete + , deleteBy + , difference + , dropEnd + , drop + , dropWhile + , filter + , filterM + , group + , groupAll + , groupAllBy + , groupBy + , mapMaybe + , partition + , pattern + , slice + -- , snoc' -- tested in onlyNonEmpty + , span + , stripPrefix + , take + , takeEnd + , takeWhile + } = do + let + l = makeCollection + + nil :: c Int + nil = l [] + + assertSkip :: forall a. Eq a => Show a => Array SkipBroken -> (_ -> AssertRec a) -> Effect Unit + assertSkip = assertSkipHelper skip + + printTestType "Only canEmpty" + + -- ======= Typeclass tests ======== + + -- Alternative + -- applicative and plus + -- (f <|> g) <*> x == (f <*> x) <|> (g <*> x) + -- empty <*> x == empty + + log "Alternative's laws should be upheld" + do -- limit scope for helper variables + let + f = l [mul 10] + g = l [mul 100] + x = l [1, 2, 3] + + -- Todo - likely broken for some list types until Apply is fixed + + assertEqual { actual: (f <|> g) <*> x, expected: (f <*> x) <|> (g <*> x) } + assertEqual { actual: (f <|> g) <*> x, expected: l [10, 20, 30, 100, 200, 300] } + assertEqual { actual: empty <*> x, expected: empty :: c Int } + + do -- limit scope for helper variable + let + f x = l [x, 10 + x] + + -- MonadZero + -- monad and alternative + -- empty >>= f = empty + log "MonadZero's law should be upheld" + assertEqual { actual: empty >>= f, expected: empty :: c Int } + + -- MonadPlus + -- Additional law on MonadZero + -- (x <|> y) >>= f == (x >>= f) <|> (y >>= f) + log "MonadPlus's law should be upheld" + let + x = l [1, 2] + y = l [3, 4] + assertEqual { actual: (x <|> y) >>= f, expected: (x >>= f) <|> (y >>= f) } + assertEqual { actual: (x <|> y) >>= f, expected: l [1,11,2,12,3,13,4,14] } + + -- Monoid + -- mempty :: c + + log "Monoid's mempty should be an empty collection" + assertEqual { actual: mempty :: c Int, expected: nil } + + log "Monoid's mempty should not change the collection it is appended to" + do -- limit scope for helper variable + let x = l [1, 2, 3] + assertEqual { actual: x <> mempty, expected: x } + assertEqual { actual: mempty <> x, expected: x } + + -- Plus + -- empty :: forall a. c a + + log "Plus's empty should be an empty collection" + assertEqual { actual: empty :: c Int, expected: nil } + + log "Plus's empty should not change the collection it is `alt`-ed (concatenated) with" + do -- limit scope for helper variable + let x = l [1, 2, 3] + assertEqual { actual: x <|> empty, expected: x } + assertEqual { actual: empty <|> x, expected: x } + + log "Plus's empty should remain unchanged when mapped over" + assertEqual { actual: (add 1) <$> empty, expected: empty :: c Int } + + + -- Unfoldable + -- unfoldr :: forall a b. (b -> Maybe (Tuple a b)) -> b -> c a + + let + step :: Int -> Maybe (Tuple Int Int) + step n = if n > 5 then Nothing else Just $ Tuple n $ n + 1 + + log "Unfoldable's unfoldr should maintain order" + assertEqual { actual: unfoldr step 1, expected: l [1, 2, 3, 4, 5] } + + log "Unfoldable's replicate should be correct" + assertEqual { actual: Unfoldable.replicate 3 1, expected: l [1, 1, 1] } + assertEqual { actual: Unfoldable.replicate 1 1, expected: l [1] } + assertEqual { actual: Unfoldable.replicate 0 1, expected: nil } + assertEqual { actual: Unfoldable.replicate (-1) 1, expected: nil } + + log "Unfoldable's replicate should be stack-safe" + assertEqual { actual: last (Unfoldable.replicate 100000 1), expected: Just 1 } + + -- ======= Functions tests ======== + + -- These functions are not available for non-empty collections + + log "null should return true if collection is empty" + assertTrue $ null nil + assertFalse $ null (l [1]) + assertFalse $ null (l [1, 2]) + + -- Todo - tests for these functions + -- many :: forall f a. Alternative f => Lazy (f (c a)) => f a -> f (c a) + -- manyRec :: forall f a. MonadRec f => Alternative f => f a -> f (c a) + + + -- These are the remaining functions that can't be deduplicated due to use of Maybe + + log "fromFoldable should create a collection from another foldable collection" + assertEqual { actual: fromFoldable [1, 2], expected: l [1, 2] } + assertEqual { actual: fromFoldable [], expected: nil } + assertEqual { actual: fromFoldable (Just 1), expected: l [1] } + assertEqual { actual: fromFoldable Nothing, expected: nil } + + -- Todo - Is this good phrasing? Might be confusing to refer to a + -- non-empty canEmpty list. + + log "head should return the first item of a non-empty collection" + assertEqual { actual: head (l [1, 2]), expected: Just 1 } + + log "head should return Nothing for an empty collection" + assertEqual { actual: head nil, expected: Nothing } + + + log "init should drop the last item of a non-empty collection" + assertEqual { actual: init (l [1, 2, 3]), expected: Just $ l [1, 2] } + + log "init should return Nothing for an empty collection" + assertEqual { actual: init nil, expected: Nothing } + + + log "last should return the last item of a non-empty collection" + assertEqual { actual: last (l [1, 2]), expected: Just 2 } + + log "last should return Nothing for an empty collection" + assertEqual { actual: last nil, expected: Nothing } + + + log "tail should drop the first item of a non-empty collection" + assertEqual { actual: tail (l [1, 2, 3]), expected: Just $ l [2, 3] } + + log "tail should return Nothing for an empty collection" + assertEqual { actual: tail nil, expected: Nothing } + + + log "uncons should split a non-empty collection into a head and tail record" + assertEqual { actual: uncons (l [1]), expected: Just { head: 1, tail: nil } } + assertEqual { actual: uncons (l [1, 2, 3]), expected: Just { head: 1, tail: l [2, 3] } } + + log "uncons should return nothing for an empty collection" + assertEqual { actual: uncons nil, expected: Nothing } + + + -- ========== Common API with additional canEmpty tests ========== + + log "Ensure common functions work with empty collections" + + -- ===== Common Typeclasses ===== + + -- Alt + -- alt :: forall a. f a -> f a -> f a + log "Alt's alt (<|>) should work with empty collections" + assertEqual { actual: l [1,2] <|> nil, expected: l [1,2] } + assertEqual { actual: nil <|> l [3,4], expected: l [3,4] } + assertEqual { actual: nil <|> nil, expected: nil } + + -- Apply + -- apply :: forall a b. f (a -> b) -> f a -> f b + log "Apply's apply (<*>) should work with empty collections" + assertEqual { actual: l [] <*> l [1, 2, 3], expected: nil } + assertEqual { actual: l [mul 10, mul 100] <*> nil, expected: nil } + + -- Bind c + -- bind :: forall a b. m a -> (a -> m b) -> m b + log "Bind's bind (>>=) should work with empty collections" + assertEqual { actual: nil >>= \x -> l [x,10+x], expected: nil } + assertEqual { actual: l [1,2,3] >>= \_ -> nil, expected: nil } + + -- Extend + -- extend :: forall b a. (w a -> b) -> w a -> w b + log "Extend's extend (<<=) should work with empty collections" + assertEqual { actual: sum <<= nil, expected: nil } + + -- Foldable + -- foldr :: forall a b. (a -> b -> b) -> b -> f a -> b + -- foldl :: forall a b. (b -> a -> b) -> b -> f a -> b + -- foldMap :: forall a m. Monoid m => (a -> m) -> f a -> m + + log "Foldable's foldl should work with empty collections" + assertEqual { actual: foldl (\b a -> b * 10 + a) 0 nil, expected: 0 } + + log "Foldable's foldr should work with empty collections" + assertEqual { actual: foldr (\a b -> b * 10 + a) 0 nil, expected: 0 } + + log "Foldable's foldMap should work with empty collections" + assertEqual { actual: foldMap show nil, expected: "" } + + -- FoldableWithIndex + -- foldrWithIndex :: forall a b. (i -> a -> b -> b) -> b -> f a -> b + -- foldlWithIndex :: forall a b. (i -> b -> a -> b) -> b -> f a -> b + -- foldMapWithIndex :: forall a m. Monoid m => (i -> a -> m) -> f a -> m + + log "FoldableWithIndex's foldlWithIndex should work with empty collections" + assertEqual { actual: foldlWithIndex (\_ b a -> b * 10 + a) 0 nil, expected: 0 } + + log "FoldableWithIndex's foldrWithIndex should work with empty collections" + assertEqual { actual: foldrWithIndex (\_ a b -> b * 10 + a) 0 nil, expected: 0 } + + log "FoldableWithIndex's foldMapWithIndex should work with empty collections" + assertEqual { actual: foldMapWithIndex (\_ a -> show a) nil, expected: "" } + + -- Functor + -- map :: forall a b. (a -> b) -> f a -> f b + log "Functor's map should work with empty collections" + assertEqual { actual: map (add 1) nil, expected: nil } + + -- FunctorWithIndex + -- mapWithIndex :: forall a b. (i -> a -> b) -> f a -> f b + + log "FunctorWithIndex's mapWithIndex should work with empty collections" + assertEqual { actual: mapWithIndex add nil, expected: nil } + + -- Ord + -- compare :: a -> a -> Ordering + log "Ord's compare should work with empty collections" + assertEqual { actual: compare nil nil, expected: EQ } + assertEqual { actual: compare nil (l [1]), expected: LT } + assertEqual { actual: compare (l [1]) nil, expected: GT } + + -- Ord1 + -- compare1 :: forall a. Ord a => f a -> f a -> Ordering + log "Ord1's compare1 should work with empty collections" + assertEqual { actual: compare1 nil nil, expected: EQ } + assertEqual { actual: compare1 nil (l [1]), expected: LT } + assertEqual { actual: compare1 (l [1]) nil, expected: GT } + + -- Semigroup + -- append :: a -> a -> a + + log "Semigroup's append (<>) should work with empty collections" + assertEqual { actual: l [1,2] <> nil, expected: l [1,2] } + assertEqual { actual: nil <> l [3,4], expected: l [3,4] } + assertEqual { actual: nil <> nil, expected: nil } + + -- Traversable + -- traverse :: forall a b m. Applicative m => (a -> m b) -> t a -> m (t b) + -- sequence :: forall a m. Applicative m => t (m a) -> m (t a) + + let + safeDiv :: Int -> Int -> Maybe Int + safeDiv _ 0 = Nothing + safeDiv n d = Just $ n / d + + log "Traversable's traverse should work with empty collections" + assertEqual { actual: traverse (safeDiv 12) nil, expected: Just nil } + + log "Traversable's sequence should work with empty collections" + assertEqual { actual: sequence (l []), expected: Just nil } + + -- TraversableWithIndex + -- traverseWithIndex :: forall a b m. Applicative m => (i -> a -> m b) -> t a -> m (t b) + + log "TraversableWithIndex's traverseWithIndex should work with empty collections" + assertEqual { actual: traverseWithIndex safeDiv nil, expected: Just nil } + + + -- ===== Common Functions ===== + + log "appendFoldable should work with empty collections" + assertEqual { actual: appendFoldable nil [4, 5], expected: l [4, 5] } + assertEqual { actual: appendFoldable nil [], expected: nil } + + log "concat should work with empty collections" + assertEqual { actual: concat (l []), expected: nil } + assertEqual { actual: concat (l [nil]), expected: nil } + assertEqual { actual: concat (l [nil, l [1, 2], nil, l [3, 4], nil]), expected: l [1, 2, 3, 4] } + + let + doubleAndOrig :: Int -> c Int + doubleAndOrig x = cons (x * 2) $ singleton x + + log "concatMap should work with empty collections" + assertEqual { actual: concatMap doubleAndOrig nil, expected: nil } + + log "cons should work with empty collections" + assertEqual { actual: cons 1 nil, expected: l [1] } + + log "elemIndex should work with empty collections" + assertEqual { actual: elemIndex 1 nil, expected: Nothing } + + log "elemLastIndex should work with empty collections" + assertEqual { actual: elemLastIndex 1 nil, expected: Nothing } + + log "findIndex should work with empty collections" + assertEqual { actual: findIndex (_ /= 1) nil, expected: Nothing } + + log "findLastIndex should work with empty collections" + assertEqual { actual: findLastIndex (_ /= 1) nil, expected: Nothing } + + log "foldM should work with empty collections" + assertEqual { actual: foldM (\x y -> Just $ x + y) 0 nil, expected: Just 0 } + + log "index (!!) should work with empty collections" + assertEqual { actual: nil `index` 0, expected: Nothing } + + log "insert should work with empty collections" + assertEqual { actual: insert 2 nil, expected: l [2] } + + log "insertBy should work with empty collections" + assertEqual { actual: insertBy (flip compare) 4 nil, expected: l [4] } + + log "intersect should work with empty collections" + assertEqual { actual: intersect nil $ l [1, 1, 2, 3], expected: nil } + assertEqual { actual: intersect (l [1, 2, 3, 4, 3, 2, 1]) nil, expected: nil } + assertEqual { actual: intersect nil nil, expected: nil } + + log "intersectBy should work with empty collections" + assertEqual { actual: intersectBy (\x y -> x * 2 == y) nil $ l [2, 6], expected: nil } + assertEqual { actual: intersectBy (\x y -> x * 2 == y) (l [1, 2, 3]) nil, expected: nil } + assertEqual { actual: intersectBy (\x y -> x * 2 == y) nil nil, expected: nil } + + log "length should work with empty collections" + assertEqual { actual: length nil, expected: 0 } + + log "nub should work with empty collections" + assertEqual { actual: nub nil, expected: nil } + + log "nubBy should work with empty collections" + assertEqual { actual: nubBy (compare `on` Array.length) $ l [], expected: l [] :: _ (_ Int) } + + log "nubEq should work with empty collections" + assertEqual { actual: nubEq nil, expected: nil } + + log "nubByEq should work with empty collections" + let mod3eq = eq `on` \n -> mod n 3 + assertEqual { actual: nubByEq mod3eq nil, expected: nil } + + log "reverse should work with empty collections" + assertEqual { actual: reverse nil, expected: nil } + + log "snoc should work with empty collections" + assertEqual { actual: nil `snoc` 4, expected: l [4] } + + -- Todo - create tests for these functions + + -- some :: forall f a. Alternative f => Lazy (f (c a)) => f a -> f (c a) + -- someRec :: forall f a. MonadRec f => Alternative f => f a -> f (c a) + + log "sort should work with empty collections" + assertEqual { actual: sort nil, expected: nil } + + log "sortBy should work with empty collections" + assertEqual { actual: sortBy (flip compare) nil, expected: nil } + + log "toUnfoldable should work with empty collections" + assertEqual { actual: toUnfoldable nil, expected: [] } + + log "transpose should work with empty collections" + assertEqual { actual: transpose (l [l [10, 11], nil, l [30, 31, 32]]) + , expected: l [l [10, 30], l [11, 31], l [32]] } + assertEqual { actual: transpose (l [] :: _ (_ Int)), expected: l []} + + log "union should work with empty collections" + assertEqual { actual: union nil $ l [1, 1], expected: l [1] } + assertEqual { actual: union (l [0, 0]) nil, expected: l [0, 0] } + assertEqual { actual: union nil nil, expected: nil } + + log "unionBy should work with empty collections" + assertEqual { actual: unionBy mod3eq nil $ l [3, 4, 3, 3], expected: l [3, 4] } + assertEqual { actual: unionBy mod3eq (l [1, 5, 1, 2]) nil, expected: l [1, 5, 1, 2] } + assertEqual { actual: unionBy mod3eq nil nil, expected: nil } + + log "unzip should work with empty collections" + assertEqual { actual: unzip (l []), expected: Tuple nil nil } + + log "zip should work with empty collections" + assertEqual { actual: zip nil $ l ["a", "b", "c"], expected: l [] } + assertEqual { actual: zip (l [1, 2, 3]) (l [] :: _ String), expected: l [] } + assertEqual { actual: zip nil (l [] :: _ String), expected: l []} + + log "zipWith should work with empty collections" + assertEqual { actual: zipWith (\x y -> l [show x, y]) nil $ l ["a", "b", "c"], expected: l [] } + assertEqual { actual: zipWith (\x y -> l [show x, y]) (l [1, 2, 3]) $ l [], expected: l [] } + assertEqual { actual: zipWith (\x y -> l [show x, y]) nil $ l [], expected: l [] } + + log "zipWithA should work with empty collections" + let + zipWithAFunc 0 _ = Nothing + zipWithAFunc x y = Just $ Tuple x y + assertEqual { actual: zipWithA zipWithAFunc nil $ l ["a", "b", "c"], expected: Just $ l [] } + assertEqual { actual: zipWithA zipWithAFunc (l [1, 2, 3]) $ l [], expected: Just $ l [] } + assertEqual { actual: zipWithA zipWithAFunc (l [1, 2, 0]) $ l [], expected: Just $ l [] } + assertEqual { actual: zipWithA zipWithAFunc nil $ l [], expected: Just $ l [] } + + -- ===== CommonDiffEmptiability Functions ===== + + log "catMaybes should work with empty collections" + assertEqual { actual: catMaybes (l []), expected: nil } + + log "delete should work with empty collections" + assertEqual { actual: delete 3 nil, expected: nil } + + log "deleteBy should work with empty collections" + assertEqual { actual: deleteBy (/=) 1 nil, expected: nil } + + log "difference (\\\\) should work with empty collections" + assertEqual { actual: l [1, 2, 3, 4, 3, 2] `difference` nil, expected: l [1, 2, 3, 4, 3, 2] } + assertEqual { actual: nil `difference` l [1, 2], expected: nil } + assertEqual { actual: nil `difference` nil, expected: nil } + + log "drop should work with empty collections" + assertEqual { actual: drop 1 nil, expected: nil } + assertEqual { actual: drop (-1) nil, expected: nil } + + log "dropEnd should work with empty collections" + assertEqual { actual: dropEnd 1 nil, expected: nil } + assertEqual { actual: dropEnd 1 nil, expected: nil } + + log "dropWhile should work with empty collections" + assertEqual { actual: dropWhile (_ /= 1) nil, expected: nil } + + log "filter should work with empty collections" + assertEqual { actual: filter odd nil, expected: nil } + + log "filterM should work with empty collections" + assertEqual { actual: filterM (Just <<< odd) nil, expected: Just $ l [] } + assertEqual { actual: filterM (const Nothing) nil, expected: Just $ l [] } + + log "group should work with empty collections" + assertEqual { actual: group nil, expected: l [] } + + log "groupAll should work with empty collections" + assertSkip [SkipBrokenLazyCanEmpty] + \_ -> { actual: groupAll nil, expected: l [] } + + log "groupAllBy should work with empty collections" + assertEqual { actual: groupAllBy (compare `on` (_ `div` 10)) nil, expected: l [] } + + log "groupBy should work with empty collections" + assertEqual { actual: groupBy (eq `on` (_ `mod` 10)) nil, expected: l [] } + + log "mapMaybe should work with empty collections" + assertEqual { actual: mapMaybe (\x -> if x /= 0 then Just x else Nothing) nil, expected: nil } + + log "partition should work with empty collections" + assertEqual { actual: partition (_ > 2) nil, expected: { yes: nil, no: nil } } + + log "slice should work with empty collections" + assertEqual { actual: slice 1 3 nil, expected: nil } + assertEqual { actual: slice (-2) 3 nil, expected: nil } + + log "span should work with empty collections" + assertEqual { actual: span (_ < 4) nil, expected: { init: nil, rest: nil } } + + log "stripPrefix should work with empty collections" + assertEqual { actual: stripPrefix (pattern nil) $ l [4, 2, 5, 1] , expected: Just $ l [4, 2, 5, 1] } + assertEqual { actual: stripPrefix (pattern (l [4, 2])) nil , expected: Nothing } + assertEqual { actual: stripPrefix (pattern nil) nil , expected: Just nil } + + log "take should work with empty collections" + assertEqual { actual: take 1 nil, expected: nil } + assertEqual { actual: take 0 nil, expected: nil } + assertEqual { actual: take (-1) nil, expected: nil } + + log "takeEnd should work with empty collections" + assertSkip [SkipBrokenLazyCanEmpty] + \_ -> { actual: takeEnd 1 nil, expected: nil } + assertSkip [SkipBrokenLazyCanEmpty] + \_ -> { actual: takeEnd 0 nil, expected: nil } + assertSkip [SkipBrokenLazyCanEmpty] + \_ -> { actual: takeEnd (-1) nil, expected: nil } + + log "takeWhile should work with empty collections" + assertEqual { actual: takeWhile (_ /= 2) nil, expected: nil } + + +-- Todo - test can-empty versions of common lazy functions + + + +testOnlyNonEmpty :: forall c canEmpty cPattern. + Comonad c => + --, Foldable1 c => -- missing from LazyNonEmptyList + --, Traversable1 c => -- missing from LazyNonEmptyList + Eq (c Int) => + Eq (canEmpty Int) => + Show (c Int) => + Show (canEmpty Int) => + -- parameters: + OnlyNonEmpty c canEmpty -> + CommonDiffEmptiability c canEmpty canEmpty c cPattern -> + Effect Unit +testOnlyNonEmpty + { makeCollection + , makeCanEmptyCollection + + -- Only available for NonEmpty collections + , fromList + , toList + -- Todo, should there be a `toUnfoldable1` function for NonEmpty collections? + + -- Can't be deduplicated from CanEmpty collections due to use of Maybe + , fromFoldable + , head + , init + , last + , tail + , uncons + } + + -- CommonDiffEmptiability + -- Only testing a few of these functions here + { cons' + , snoc' + } = do + let + l = makeCollection + cel = makeCanEmptyCollection + + nil :: canEmpty Int + nil = cel [] + + printTestType "Only nonEmpty" + + -- ======= Typeclass tests ======== + + -- Todo + + -- Comonad + -- Foldable1 + -- Traversable1 + + -- ======= Functions tests ======== + + -- These functions are only available for NonEmpty collections + + log "fromList should convert from a List to a NonEmptyList" + assertEqual { actual: fromList (cel [1, 2, 3]), expected: Just $ l [1, 2, 3] } + assertEqual { actual: fromList nil, expected: Nothing } + + log "toList should convert from a NonEmptyList to a List" + assertEqual { actual: toList (l [1, 2, 3]), expected: cel [1, 2, 3] } + + + -- These are the remaining functions that can't be deduplicated due to use of Maybe + + log "fromFoldable should create a collection from another foldable collection" + assertEqual { actual: fromFoldable [1, 2], expected: Just $ l [1, 2] } + assertEqual { actual: fromFoldable ([] :: _ Int), expected: Nothing } + assertEqual { actual: fromFoldable (Just 1), expected: Just $ l [1] } + assertEqual { actual: fromFoldable (Nothing :: _ Int), expected: Nothing } + + + log "head should return a the first value" + assertEqual { actual: head (l [1, 2]), expected: 1 } + + log "init should return a canEmpty collection of all but the last value" + assertEqual { actual: init (l [1, 2, 3]), expected: cel [1, 2] } + + log "last should return the last value" + assertEqual { actual: last (l [1, 2]), expected: 2 } + + log "tail should return a canEmpty collection of all but the first value" + assertEqual { actual: tail (l [1, 2, 3]), expected: cel [2, 3] } + + log "uncons should split a collection into a record containing the first and remaining values" + assertEqual { actual: uncons (l [1]), expected: { head: 1, tail: nil } } + assertEqual { actual: uncons (l [1, 2, 3]), expected: { head: 1, tail: cel [2, 3] } } + + + -- ========== Common API with additional canEmpty tests ========== + -- Note that cons' and snoc' must be tested here, rather than in + -- canEmpty, because we want a canEmpty arg and nonEmpty returned collection. + + log "cons' should work with empty collections" + assertEqual { actual: cons' 1 nil, expected: l [1] } + + log "snoc' should work with empty collections" + assertEqual { actual: snoc' nil 3, expected: l [3] } + + + + +testOnlyLazy :: forall c. + Lazy (c Int) => -- Todo - missing from LazyNonEmptyList + Eq (c Int) => + Show (c Int) => + OnlyLazy c -> + Common c -> + Effect Unit +testOnlyLazy + { makeCollection + , takeSimple + + -- Same names, but different APIs (without Maybe) + , insertAt + , modifyAt + , updateAt + + -- These are only available for Lazy collections + , iterate + , repeat + , cycle + , foldrLazy + , scanlLazy + + -- Specialized from Unfoldable1's replicate1 / replicate1A + , replicate1 + , replicate1M + } + + -- Reusing some Common functions for these Lazy tests. + -- Note, must use `com` named pattern to work around this bug: + -- https://github.com/purescript/purescript/issues/3938 + com@{ + cons + , length + , singleton + } = do + let + l = makeCollection + + printTestType "Only Lazy" + + + log "insertAt should add an item at the specified index" + assertEqual { actual: insertAt 0 1 $ l [2, 3], expected: l [1, 2, 3] } + assertEqual { actual: insertAt 1 1 $ l [2, 3], expected: l [2, 1, 3] } + assertEqual { actual: insertAt 2 1 $ l [2, 3], expected: l [2, 3, 1] } + + log "insertAt should return the original collection if the index is out of range" + assertEqual { actual: insertAt 7 8 $ l [1,2,3], expected: l [1,2,3] } + assertEqual { actual: insertAt (-1) 8 $ l [1,2,3], expected: l [1,2,3] } + + + log "modifyAt should update an item at the specified index" + assertEqual { actual: modifyAt 0 (_ + 1) $ l [1, 2, 3], expected: l [2, 2, 3] } + assertEqual { actual: modifyAt 1 (_ + 1) $ l [1, 2, 3], expected: l [1, 3, 3] } + + log "modifyAt should return the original collection if the index is out of range" + assertEqual { actual: modifyAt 7 (_ + 1) $ l [1,2,3], expected: l [1,2,3] } + assertEqual { actual: modifyAt (-1) (_ + 1) $ l [1,2,3], expected: l [1,2,3] } + + + log "updateAt should replace an item at the specified index" + assertEqual { actual: updateAt 0 9 $ l [1, 2, 3], expected: l [9, 2, 3] } + assertEqual { actual: updateAt 1 9 $ l [1, 2, 3], expected: l [1, 9, 3] } + + log "updateAt should return the original collection if the index is out of range" + assertEqual { actual: updateAt 5 9 $ l [1, 2, 3], expected: l [1, 2, 3] } + assertEqual { actual: updateAt (-1) 9 $ l [1, 2, 3], expected: l [1, 2, 3] } + + let + t5 = takeSimple 5 + + log "repeate should create an infinite collection of a repeated element" + assertEqual { actual: t5 $ repeat 2, expected: l [2, 2, 2, 2, 2] } + + log "cycle should create an infinite collection by repeating another collection" + assertEqual { actual: t5 $ cycle (l [1, 2, 3]), expected: l [1, 2, 3, 1, 2] } + + log "iterate should create an infinite collection by iterating a function" + assertEqual { actual: t5 $ iterate (mul 2) 3, expected: l [3, 6, 12, 24, 48] } + + + log "foldrLazy should be correct" + assertEqual + { actual: foldrLazy (\a b -> (a * 2) `cons` b) (singleton (-1)) $ l [1, 2, 3] + , expected: l [2, 4, 6, -1] + } + + log "foldrLazy should work ok on infinite lists" + assertEqual + { actual: takeSimple 3 $ foldrLazy (\a b -> (a * 2) `cons` b) (singleton (-1)) $ iterate (add 1) 1 + , expected: l [2, 4, 6] + } + + + log "scanlLazy should be correct" + assertEqual + { actual: scanlLazy add 5 $ l [1, 2, 3, 4] + , expected: l [6, 8, 11, 15] + } + + log "scanlLazy should work ok on infinite lists" + assertEqual + { actual: takeSimple 4 $ scanlLazy add 5 $ iterate (add 1) 1 + , expected: l [6, 8, 11, 15] + } + + + log "replicate1 should be correct" + assertEqual { actual: replicate1 3 1, expected: l [1, 1, 1] } + assertEqual { actual: replicate1 1 1, expected: l [1] } + assertEqual { actual: replicate1 0 1, expected: l [1] } + assertEqual { actual: replicate1 (-1) 1, expected: l [1] } + + log "replicate1 should be stack-safe" + assertEqual { actual: length (replicate1 100000 1), expected: 100000 } + + log "replicate1 should be lazy" + assertEqual { actual: takeSimple 3 $ replicate1 100000000 1, expected: l [1, 1, 1] } + + + log "replicate1M should be correct" + assertEqual { actual: replicate1M 3 $ Just 1, expected: Just $ l [1, 1, 1] } + assertEqual { actual: replicate1M 1 $ Just 1, expected: Just $ l [1] } + assertEqual { actual: replicate1M 0 $ Just 1, expected: Just $ l [1] } + assertEqual { actual: replicate1M (-1) $ Just 1, expected: Just $ l [1] } + + assertEqual { actual: replicate1M 3 (Nothing :: _ Int), expected: Nothing } + assertEqual { actual: replicate1M 1 (Nothing :: _ Int), expected: Nothing } + assertEqual { actual: replicate1M 0 (Nothing :: _ Int), expected: Nothing } + assertEqual { actual: replicate1M (-1) (Nothing :: _ Int), expected: Nothing } + + log "replicate1M should be stack-safe" + -- Must use com. here because of this typechecker bug: + -- https://github.com/purescript/purescript/issues/3938#issuecomment-880390437 + assertEqual { actual: map com.length (replicate1M 100000 (Just 1)), expected: Just 100000 } + assertEqual { actual: map com.length (replicate1M 100000 (Nothing :: _ Int)), expected: Nothing } + + log "replicate1M should be lazy" + assertEqual { actual: map (takeSimple 3) $ replicate1M 100000000 $ Just 1, expected: Just $ l [1, 1, 1] } + assertEqual { actual: map (takeSimple 3) $ replicate1M 100000000 $ (Nothing :: _ Int), expected: Nothing } + + + -- Additional common tests for lazy collections + + -- todo, reorder + + log "nub should not consume more of the input list than necessary" + assertEqual + { actual: takeSimple 3 $ com.nub (cycle (l [1, 2, 3])) + , expected: l [1, 2, 3] + } + + + -- Todo - nubEq, etc + + + let + nonZeroAdd 0 _ = Nothing + nonZeroAdd _ 0 = Nothing + nonZeroAdd x y = Just $ x + y + + log "zipWithA should work with infinite lists" + assertEqual { actual: com.zipWithA nonZeroAdd (repeat 1) $ l [1, 2, 3], expected: Just $ l [11, 12, 13] } + assertEqual { actual: com.zipWithA nonZeroAdd (l [1, 2, 3]) $ repeat 1, expected: Just $ l [11, 12, 13] } + assertEqual { actual: com.zipWithA nonZeroAdd (repeat 1) $ cycle (l [1, 2, 0]), expected: Nothing } + + log "foldM should work ok on infinite lists" + assertEqual { actual: com.foldM nonZeroAdd 1 $ cycle (l [1, 2, 0]), expected: Nothing } + + log "range should be lazy" + assertEqual { actual: takeSimple 3 $ com.range 0 100000000, expected: l [0, 1, 2] } + +testOnlyStrict :: forall c. + Eq (c Int) => + Show (c Int) => + OnlyStrict c -> Effect Unit +testOnlyStrict + { makeCollection + + -- Same names, but different APIs (with Maybe) + , insertAt + , modifyAt + , updateAt + + } = do + + let + l = makeCollection + + printTestType "Only Strict" + + log "insertAt should add an item at the specified index" + assertEqual { actual: insertAt 0 1 $ l [2, 3], expected: Just $ l [1, 2, 3] } + assertEqual { actual: insertAt 1 1 $ l [2, 3], expected: Just $ l [2, 1, 3] } + assertEqual { actual: insertAt 2 1 $ l [2, 3], expected: Just $ l [2, 3, 1] } + + log "insertAt should return Nothing if the index is out of range" + assertEqual { actual: insertAt 7 8 $ l [1,2,3], expected: Nothing } + assertEqual { actual: insertAt (-1) 8 $ l [1,2,3], expected: Nothing } + + log "modifyAt should update an item at the specified index" + assertEqual { actual: modifyAt 0 (_ + 1) $ l [1, 2, 3], expected: Just $ l [2, 2, 3] } + assertEqual { actual: modifyAt 1 (_ + 1) $ l [1, 2, 3], expected: Just $ l [1, 3, 3] } + + log "modifyAt should return Nothing if the index is out of range" + assertEqual { actual: modifyAt 7 (_ + 1) $ l [1,2,3], expected: Nothing } + assertEqual { actual: modifyAt (-1) (_ + 1) $ l [1,2,3], expected: Nothing } + + log "updateAt should replace an item at the specified index" + assertEqual { actual: updateAt 0 9 $ l [1, 2, 3], expected: Just $ l [9, 2, 3] } + assertEqual { actual: updateAt 1 9 $ l [1, 2, 3], expected: Just $ l [1, 9, 3] } + + log "updateAt should return Nothing if the index is out of range" + assertEqual { actual: updateAt 5 9 $ l [1, 2, 3], expected: Nothing } + assertEqual { actual: updateAt (-1) 9 $ l [1, 2, 3], expected: Nothing } + + + +-- Functions that cannot be tested generically. + +-- helper func +removeZerosAndDouble :: Int -> Maybe Int +removeZerosAndDouble 0 = Nothing +removeZerosAndDouble n = Just $ 2 * n + +testOnlyStrictCanEmpty :: OnlyStrictCanEmpty L.List -> Effect Unit +testOnlyStrictCanEmpty + { alterAt + , deleteAt + } = do + + let + l :: forall f a. Foldable f => f a -> L.List a + l = L.fromFoldable + + printTestType "Only Strict canEmpty" + + -- Common function names, but different signatures + + log "alterAt should remove an item at the specified index" + assertEqual { actual: alterAt 0 removeZerosAndDouble $ l [1, 2, 3], expected: Just $ l [2, 2, 3] } + assertEqual { actual: alterAt 1 removeZerosAndDouble $ l [1, 0, 3], expected: Just $ l [1, 3] } + + log "alterAt should return Nothing if index is out of bounds" + assertEqual { actual: alterAt 5 removeZerosAndDouble $ l [1, 2, 3], expected: Nothing } + assertEqual { actual: alterAt (-1) removeZerosAndDouble $ l [1, 2, 3], expected: Nothing } + + + log "deleteAt should remove an item at the specified index" + assertEqual { actual: deleteAt 0 $ l [1, 2, 3], expected: Just $ l [2, 3] } + assertEqual { actual: deleteAt 1 $ l [1, 2, 3], expected: Just $ l [1, 3] } + + log "deleteAt should return Nothing if index is out of bounds" + assertEqual { actual: deleteAt 5 $ l [1, 2, 3], expected: Nothing } + assertEqual { actual: deleteAt (-1) $ l [1, 2, 3], expected: Nothing } + + +testOnlyStrictNonEmpty :: OnlyStrictNonEmpty NEL.NonEmptyList L.List -> Effect Unit +testOnlyStrictNonEmpty + { alterAt + , deleteAt + } = do + + let + l :: forall f a. Foldable f => f a -> NEL.NonEmptyList a + l = unsafePartial fromJust <<< NEL.fromFoldable + + cel :: forall f a. Foldable f => f a -> L.List a + cel = L.fromFoldable + + printTestType "Only Strict NonEmpty" + + -- Common function names, but different signatures + + log "alterAt should remove an item at the specified index" + assertEqual { actual: alterAt 0 removeZerosAndDouble $ l [1, 2, 3], expected: Just $ cel [2, 2, 3] } + assertEqual { actual: alterAt 1 removeZerosAndDouble $ l [1, 0, 3], expected: Just $ cel [1, 3] } + + log "alterAt should return Nothing if index is out of bounds" + assertEqual { actual: alterAt 5 removeZerosAndDouble $ l [1, 2, 3], expected: Nothing } + assertEqual { actual: alterAt (-1) removeZerosAndDouble $ l [1, 2, 3], expected: Nothing } + + + log "deleteAt should remove an item at the specified index" + assertSkipAlways \_ -> { actual: deleteAt 0 $ l [1, 2, 3], expected: Just $ cel [2, 3] } + assertSkipAlways \_ -> { actual: deleteAt 1 $ l [1, 2, 3], expected: Just $ cel [1, 3] } + + log "deleteAt should return Nothing if index is out of bounds" + assertSkipAlways \_ -> { actual: deleteAt 5 $ l [1, 2, 3], expected: Nothing } + assertSkipAlways \_ -> { actual: deleteAt (-1) $ l [1, 2, 3], expected: Nothing } + + +testOnlyLazyCanEmpty :: OnlyLazyCanEmpty LL.List -> Effect Unit +testOnlyLazyCanEmpty + -- Common function names, but different signatures + { alterAt + , deleteAt + -- Unique functions + , replicate + , replicateM + } = do + + let + l :: forall f a. Foldable f => f a -> LL.List a + l = LL.fromFoldable + + nil = l [] + + printTestType "Only Lazy canEmpty" + + -- Common function names, but different signatures + + log "alterAt should remove an item at the specified index" + assertEqual { actual: alterAt 0 removeZerosAndDouble $ l [1, 2, 3], expected: l [2, 2, 3] } + assertEqual { actual: alterAt 1 removeZerosAndDouble $ l [1, 0, 3], expected: l [1, 3] } + + log "alterAt should return the original collection if index is out of bounds" + assertEqual { actual: alterAt 5 removeZerosAndDouble $ l [1, 2, 3], expected: l [1, 2, 3] } + assertEqual { actual: alterAt (-1) removeZerosAndDouble $ l [1, 2, 3], expected: l [1, 2, 3] } + + + log "deleteAt should remove an item at the specified index" + assertEqual { actual: deleteAt 0 $ l [1, 2, 3], expected: l [2, 3] } + assertEqual { actual: deleteAt 1 $ l [1, 2, 3], expected: l [1, 3] } + + log "deleteAt should return the original collection if index is out of bounds" + assertEqual { actual: deleteAt 5 $ l [1, 2, 3], expected: l [1, 2, 3] } + assertEqual { actual: deleteAt (-1) $ l [1, 2, 3], expected: l [1, 2, 3] } + + -- Unique functions + + log "replicate should be correct" + assertEqual { actual: replicate 3 1, expected: l [1, 1, 1] } + assertEqual { actual: replicate 1 1, expected: l [1] } + assertEqual { actual: replicate 0 1, expected: nil } + assertEqual { actual: replicate (-1) 1, expected: nil } + + log "replicate should be stack-safe" + assertEqual { actual: LL.length (replicate 100000 1), expected: 100000 } + + log "replicate should be lazy" + assertEqual { actual: LL.take 3 $ replicate 100000000 1, expected: l [1, 1, 1] } + + + log "replicate should be correct" + assertEqual { actual: replicateM 3 $ Just 1, expected: Just $ l [1, 1, 1] } + assertEqual { actual: replicateM 1 $ Just 1, expected: Just $ l [1] } + assertEqual { actual: replicateM 0 $ Just 1, expected: Just nil } + assertEqual { actual: replicateM (-1) $ Just 1, expected: Just nil } + + assertEqual { actual: replicateM 3 (Nothing :: _ Int), expected: Nothing } + assertEqual { actual: replicateM 1 (Nothing :: _ Int), expected: Nothing } + assertEqual { actual: replicateM 0 (Nothing :: _ Int), expected: Nothing } + assertEqual { actual: replicateM (-1) (Nothing :: _ Int), expected: Nothing } + + log "replicateM should be stack-safe" + assertEqual { actual: map LL.length (replicateM 100000 (Just 1)), expected: Just 100000 } + assertEqual { actual: map LL.length (replicateM 100000 (Nothing :: _ Int)), expected: Nothing } + + log "replicateM should be lazy" + assertEqual { actual: map (LL.take 3) $ replicateM 100000000 $ Just 1, expected: Just $ l [1, 1, 1] } + assertEqual { actual: map (LL.take 3) $ replicateM 100000000 $ (Nothing :: _ Int), expected: Nothing } + +{- + -- This currently only works for lazy-can-empty. + -- It also requires access to some additional common functions. + -- Might not be worth keeping. + + log "can find the first 10 primes using lazy collections" + let eratos :: c Int -> c Int + eratos xs = defer \_ -> + case com.uncons xs of + Nothing -> nil + Just { head: p, tail: ys } -> p `cons` eratos (filter (\x -> x `mod` p /= 0) ys) + + primes = eratos $ iterate (add 1) 2 + + assertEqual { actual: takeSimple 10 primes, expected: l [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] } +-} + + + +testOnlyLazyNonEmpty :: OnlyLazyNonEmpty LNEL.NonEmptyList LL.List -> Effect Unit +testOnlyLazyNonEmpty + { alterAt + , deleteAt + } = do + + let + l :: forall f a. Foldable f => f a -> LNEL.NonEmptyList a + l = unsafePartial fromJust <<< LNEL.fromFoldable + + cel :: forall f a. Foldable f => f a -> LL.List a + cel = LL.fromFoldable + + printTestType "Only Lazy NonEmpty" + + -- Common function names, but different signatures + + log "alterAt should remove an item at the specified index" + assertEqual { actual: alterAt 0 removeZerosAndDouble $ l [1, 2, 3], expected: cel [2, 2, 3] } + assertEqual { actual: alterAt 1 removeZerosAndDouble $ l [1, 0, 3], expected: cel [1, 3] } + + log "alterAt should return the original collection if index is out of bounds" + assertEqual { actual: alterAt 5 removeZerosAndDouble $ l [1, 2, 3], expected: cel [1, 2, 3] } + assertEqual { actual: alterAt (-1) removeZerosAndDouble $ l [1, 2, 3], expected: cel [1, 2, 3] } + + + log "deleteAt should remove an item at the specified index" + assertEqual { actual: deleteAt 0 $ l [1, 2, 3], expected: cel [2, 3] } + assertEqual { actual: deleteAt 1 $ l [1, 2, 3], expected: cel [1, 3] } + + log "deleteAt should return the original collection if index is out of bounds" + assertEqual { actual: deleteAt 5 $ l [1, 2, 3], expected: cel [1, 2, 3] } + assertEqual { actual: deleteAt (-1) $ l [1, 2, 3], expected: cel [1, 2, 3] } diff --git a/test/Test/Args/LazyList.purs b/test/Test/Args/LazyList.purs new file mode 100644 index 0000000..2f8ffc6 --- /dev/null +++ b/test/Test/Args/LazyList.purs @@ -0,0 +1,139 @@ +module Test.Args.LazyList where + +import Data.List.Lazy + +import Data.Foldable (class Foldable) +import Data.List.Lazy.NonEmpty as NEL +import Data.Maybe (fromJust) +import Partial.Unsafe (unsafePartial) +import Prelude ((<<<)) +import Test.API (Common, CommonDiffEmptiability, OnlyCanEmpty, OnlyLazy, OnlyLazyCanEmpty) + +makeCollection :: forall a f. Foldable f => f a -> List a +makeCollection = fromFoldable + +makeCanEmptyCollection :: forall a f. Foldable f => f a -> List a +makeCanEmptyCollection = fromFoldable + +makeNonEmptyCollection :: forall a f. Foldable f => f a -> NEL.NonEmptyList a +makeNonEmptyCollection = unsafePartial fromJust <<< NEL.fromFoldable + + +common :: Common List +common = + { makeCollection + + , concat + , concatMap + , cons + , elemIndex + , elemLastIndex + , findIndex + , findLastIndex + , foldM + , index + , intersect + , intersectBy + , length + , nubEq + , nubByEq + , range + , reverse + , singleton + , snoc + , toUnfoldable + , union + , unionBy + , unzip + , zip + , zipWith + , zipWithA + + , appendFoldable + , insert + , insertBy + , nub + , nubBy + , some + , someRec + , sort + , sortBy + , transpose + } + +commonDiffEmptiability :: CommonDiffEmptiability List NEL.NonEmptyList List NEL.NonEmptyList Pattern +commonDiffEmptiability = + { makeCollection + , makeCanEmptyCollection + , makeNonEmptyCollection + , makeInverseCollection: makeNonEmptyCollection + + , catMaybes + , drop + , dropWhile + , filter + , filterM + , group + , groupAll + , groupBy + , mapMaybe + , partition + , span + , take + , takeEnd + , takeWhile + + , cons' + , delete + , deleteBy + , difference + , dropEnd + , groupAllBy + , pattern: Pattern + , slice + , snoc' + , stripPrefix + } + +onlyCanEmpty :: OnlyCanEmpty List +onlyCanEmpty = + { makeCollection + + , fromFoldable + , head + , init + , last + , tail + , uncons + + , null + , many + , manyRec + } + +onlyLazy :: OnlyLazy List +onlyLazy = + { makeCollection + , takeSimple: take + + , insertAt + , modifyAt + , updateAt + + , iterate + , repeat + , cycle + , foldrLazy + , scanlLazy + + , replicate1 + , replicate1M + } + +onlyLazyCanEmpty :: OnlyLazyCanEmpty List +onlyLazyCanEmpty = + { alterAt + , deleteAt + , replicate + , replicateM + } \ No newline at end of file diff --git a/test/Test/Args/LazyNonEmptyList.purs b/test/Test/Args/LazyNonEmptyList.purs new file mode 100644 index 0000000..87c2775 --- /dev/null +++ b/test/Test/Args/LazyNonEmptyList.purs @@ -0,0 +1,140 @@ +module Test.Args.LazyNonEmptyList where + +import Data.List.Lazy.NonEmpty + +import Data.Foldable (class Foldable) +import Data.List.Lazy as L +import Data.Maybe (fromJust) +import Partial.Unsafe (unsafePartial) +import Prelude ((<<<)) +import Test.API (Common, CommonDiffEmptiability, OnlyLazy, OnlyNonEmpty, OnlyLazyNonEmpty) + +makeCollection :: forall a f. Foldable f => f a -> NonEmptyList a +makeCollection = unsafePartial fromJust <<< fromFoldable + +makeCanEmptyCollection :: forall a f. Foldable f => f a -> L.List a +makeCanEmptyCollection = L.fromFoldable + +makeNonEmptyCollection :: forall a f. Foldable f => f a -> NonEmptyList a +makeNonEmptyCollection = makeCollection + +-- Suppress conversion to canEmpty list to enable common testing code +takeSimple :: forall a. Int -> NonEmptyList a -> NonEmptyList a +takeSimple n = unsafePartial fromJust <<< fromList <<< take n + +common :: Common NonEmptyList +common = + { makeCollection + + , concat + , concatMap + , cons + , elemIndex + , elemLastIndex + , findIndex + , findLastIndex + , foldM + , index + , intersect + , intersectBy + , length + , nubEq + , nubByEq + , range + , reverse + , singleton + , snoc + , toUnfoldable + , union + , unionBy + , unzip + , zip + , zipWith + , zipWithA + + , appendFoldable + , insert + , insertBy + , nub + , nubBy + , some + , someRec + , sort + , sortBy + , transpose + } + +commonDiffEmptiability :: CommonDiffEmptiability NonEmptyList L.List L.List NonEmptyList Pattern +commonDiffEmptiability = + { makeCollection + , makeCanEmptyCollection + , makeNonEmptyCollection + , makeInverseCollection: makeCanEmptyCollection + + , catMaybes + , drop + , dropWhile + , filter + , filterM + , group + , groupAll + , groupBy + , mapMaybe + , partition + , span + , take + , takeEnd + , takeWhile + + , cons' + , delete + , deleteBy + , difference + , dropEnd + , groupAllBy + , pattern: Pattern + , slice + , snoc' + , stripPrefix + } + +onlyNonEmpty :: OnlyNonEmpty NonEmptyList L.List +onlyNonEmpty = + { makeCollection + , makeCanEmptyCollection + + , fromFoldable + , head + , init + , last + , tail + , uncons + + , fromList + , toList + } + +onlyLazy :: OnlyLazy NonEmptyList +onlyLazy = + { makeCollection + , takeSimple + + , insertAt + , modifyAt + , updateAt + + , iterate + , repeat + , cycle + , foldrLazy + , scanlLazy + + , replicate1 + , replicate1M + } + +onlyLazyNonEmpty :: OnlyLazyNonEmpty NonEmptyList L.List +onlyLazyNonEmpty = + { alterAt + , deleteAt + } \ No newline at end of file diff --git a/test/Test/Args/List.purs b/test/Test/Args/List.purs new file mode 100644 index 0000000..d93f373 --- /dev/null +++ b/test/Test/Args/List.purs @@ -0,0 +1,126 @@ +module Test.Args.List where + +import Data.List + +import Data.Foldable (class Foldable) +import Data.List.NonEmpty as NEL +import Data.Maybe (fromJust) +import Partial.Unsafe (unsafePartial) +import Prelude ((<<<)) +import Test.API (Common, CommonDiffEmptiability, OnlyCanEmpty, OnlyStrict, OnlyStrictCanEmpty) + +makeCollection :: forall a f. Foldable f => f a -> List a +makeCollection = fromFoldable + +makeCanEmptyCollection :: forall a f. Foldable f => f a -> List a +makeCanEmptyCollection = fromFoldable + +makeNonEmptyCollection :: forall a f. Foldable f => f a -> NEL.NonEmptyList a +makeNonEmptyCollection = unsafePartial fromJust <<< NEL.fromFoldable + +common :: Common List +common = + { makeCollection + + , concat + , concatMap + , cons: Cons + , elemIndex + , elemLastIndex + , findIndex + , findLastIndex + , foldM + , index + , intersect + , intersectBy + , length + , nubEq + , nubByEq + , range + , reverse + , singleton + , snoc + , toUnfoldable + , union + , unionBy + , unzip + , zip + , zipWith + , zipWithA + + , appendFoldable + , insert + , insertBy + , nub + , nubBy + , some + , someRec + , sort + , sortBy + , transpose + } + +commonDiffEmptiability :: CommonDiffEmptiability List NEL.NonEmptyList List NEL.NonEmptyList Pattern +commonDiffEmptiability = + { makeCollection + , makeCanEmptyCollection + , makeNonEmptyCollection + , makeInverseCollection: makeNonEmptyCollection + + , catMaybes + , drop + , dropWhile + , filter + , filterM + , group + , groupAll + , groupBy + , mapMaybe + , partition + , span + , take + , takeEnd + , takeWhile + + , cons' + , delete + , deleteBy + , difference + , dropEnd + , groupAllBy + , pattern: Pattern + , slice + , snoc' + , stripPrefix + } + +onlyCanEmpty :: OnlyCanEmpty List +onlyCanEmpty = + { makeCollection + + , fromFoldable + , head + , init + , last + , tail + , uncons + + , null + , many + , manyRec + } + +onlyStrict :: OnlyStrict List +onlyStrict = + { makeCollection + + , insertAt + , modifyAt + , updateAt + } + +onlyStrictCanEmpty :: OnlyStrictCanEmpty List +onlyStrictCanEmpty = + { alterAt + , deleteAt + } \ No newline at end of file diff --git a/test/Test/Args/NonEmptyList.purs b/test/Test/Args/NonEmptyList.purs new file mode 100644 index 0000000..cffd296 --- /dev/null +++ b/test/Test/Args/NonEmptyList.purs @@ -0,0 +1,126 @@ +module Test.Args.NonEmptyList where + +import Data.List.NonEmpty + +import Data.Foldable (class Foldable) +import Data.List as L +import Data.Maybe (fromJust) +import Partial.Unsafe (unsafePartial) +import Prelude ((<<<)) +import Test.API (Common, CommonDiffEmptiability, OnlyNonEmpty, OnlyStrict, OnlyStrictNonEmpty) + +makeCollection :: forall a f. Foldable f => f a -> NonEmptyList a +makeCollection = unsafePartial fromJust <<< fromFoldable + +makeCanEmptyCollection :: forall a f. Foldable f => f a -> L.List a +makeCanEmptyCollection = L.fromFoldable + +makeNonEmptyCollection :: forall a f. Foldable f => f a -> NonEmptyList a +makeNonEmptyCollection = makeCollection + +common :: Common NonEmptyList +common = + { makeCollection + + , concat + , concatMap + , cons + , elemIndex + , elemLastIndex + , findIndex + , findLastIndex + , foldM + , index + , intersect + , intersectBy + , length + , nubEq + , nubByEq + , range + , reverse + , singleton + , snoc + , toUnfoldable + , union + , unionBy + , unzip + , zip + , zipWith + , zipWithA + + , appendFoldable + , insert + , insertBy + , nub + , nubBy + , some + , someRec + , sort + , sortBy + , transpose + } + +commonDiffEmptiability :: CommonDiffEmptiability NonEmptyList L.List L.List NonEmptyList Pattern +commonDiffEmptiability = + { makeCollection + , makeCanEmptyCollection + , makeNonEmptyCollection + , makeInverseCollection: makeCanEmptyCollection + + , catMaybes + , drop + , dropWhile + , filter + , filterM + , group + , groupAll + , groupBy + , mapMaybe + , partition + , span + , take + , takeEnd + , takeWhile + + , cons' + , delete + , deleteBy + , difference + , dropEnd + , groupAllBy + , pattern: Pattern + , slice + , snoc' + , stripPrefix + } + +onlyNonEmpty :: OnlyNonEmpty NonEmptyList L.List +onlyNonEmpty = + { makeCollection + , makeCanEmptyCollection + + , fromFoldable + , head + , init + , last + , tail + , uncons + + , fromList + , toList + } + +onlyStrict :: OnlyStrict NonEmptyList +onlyStrict = + { makeCollection + + , insertAt + , modifyAt + , updateAt + } + +onlyStrictNonEmpty :: OnlyStrictNonEmpty NonEmptyList L.List +onlyStrictNonEmpty = + { alterAt + , deleteAt + } \ No newline at end of file diff --git a/test/Test/Data/List.purs b/test/Test/Data/List.purs index 5ac2db8..753d451 100644 --- a/test/Test/Data/List.purs +++ b/test/Test/Data/List.purs @@ -3,7 +3,7 @@ module Test.Data.List (testList) where import Prelude import Data.Array as Array -import Data.Foldable (foldMap, foldl) +import Data.Foldable (class Foldable, foldMap, foldl) import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex) import Data.Function (on) import Data.List (List(..), Pattern(..), alterAt, catMaybes, concat, concatMap, delete, deleteAt, deleteBy, drop, dropEnd, dropWhile, elemIndex, elemLastIndex, filter, filterM, findIndex, findLastIndex, foldM, fromFoldable, group, groupAll, groupAllBy, groupBy, head, init, insert, insertAt, insertBy, intersect, intersectBy, last, length, mapMaybe, mapWithIndex, modifyAt, nub, nubBy, nubByEq, nubEq, null, partition, range, reverse, singleton, snoc, sort, sortBy, span, stripPrefix, tail, take, takeEnd, takeWhile, transpose, uncons, union, unionBy, unsnoc, unzip, updateAt, zip, zipWith, zipWithA, (!!), (..), (:), (\\)) @@ -23,7 +23,11 @@ import Test.Assert (assert) testList :: Effect Unit testList = do - let l = fromFoldable + let + l = fromFoldable + + nel :: forall f a. Foldable f => a -> f a -> NEL.NonEmptyList a + nel x xs = NEL.NonEmptyList $ x :| fromFoldable xs log "strip prefix" assert $ stripPrefix (Pattern (1:Nil)) (1:2:Nil) == Just (2:Nil) @@ -275,8 +279,8 @@ testList = do log "groupBy should group consecutive equal elements into lists based on an equivalence relation" assert $ groupBy (\x y -> odd x && odd y) (l [1, 1, 2, 2, 3, 3]) == l [NEL.NonEmptyList (1 :| l [1]), NEL.singleton 2, NEL.singleton 2, NEL.NonEmptyList (3 :| l [3])] - log "groupAllBy should group equal elements into lists based on an equivalence relation" - assert $ groupAllBy (\x y -> odd x && odd y) (l [1, 3, 2, 4, 3, 3]) == l [NEL.singleton 1, NEL.singleton 2, NEL.NonEmptyList (3 :| l [3, 3]), NEL.singleton 4] + log "groupAllBy should sort then group equal elements into lists based on a comparison function" + assert $ groupAllBy (compare `on` (_ `div` 10)) (l [32, 31, 21, 22, 11, 33]) == l [nel 11 [], nel 21 [22], nel 32 [31, 33]] log "partition should separate a list into a tuple of lists that do and do not satisfy a predicate" let partitioned = partition (_ > 2) (l [1, 5, 3, 2, 4]) diff --git a/test/Test/Data/List/NonEmpty.purs b/test/Test/Data/List/NonEmpty.purs index b12380a..3a342f9 100644 --- a/test/Test/Data/List/NonEmpty.purs +++ b/test/Test/Data/List/NonEmpty.purs @@ -21,9 +21,9 @@ import Test.Assert (assert) testNonEmptyList :: Effect Unit testNonEmptyList = do let - nel :: ∀ f a. Foldable f => a -> f a -> NEL.NonEmptyList a + nel :: forall f a. Foldable f => a -> f a -> NEL.NonEmptyList a nel x xs = NEL.NonEmptyList $ x :| L.fromFoldable xs - l :: ∀ f a. Foldable f => f a -> L.List a + l :: forall f a. Foldable f => f a -> L.List a l = L.fromFoldable log "singleton should construct a non-empty list with a single value" @@ -176,8 +176,8 @@ testNonEmptyList = do log "groupBy should group consecutive equal elements into lists based on an equivalence relation" assert $ NEL.groupBy (\x y -> odd x && odd y) (nel 1 [1, 2, 2, 3, 3]) == nel (nel 1 [1]) [nel 2 [], nel 2 [], nel 3 [3]] - log "groupAllBy should group equal elements into lists based on an equivalence relation" - assert $ NEL.groupAllBy (\x y -> odd x && odd y) (nel 1 [3, 2, 4, 3, 3]) == nel (nel 1 []) [nel 2 [], nel 3 [3, 3], nel 4 []] + log "groupAllBy should sort then group equal elements into lists based on a comparison function" + assert $ NEL.groupAllBy (compare `on` (_ `div` 10)) (nel 32 [31, 21, 22, 11, 33]) == nel (nel 11 []) [nel 21 [22], nel 32 [31, 33]] log "partition should separate a list into a tuple of lists that do and do not satisfy a predicate" let partitioned = NEL.partition (_ > 2) (nel 1 [5, 3, 2, 4]) diff --git a/test/Test/Main.purs b/test/Test/Main.purs index 096e807..552577a 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -4,16 +4,25 @@ import Prelude import Effect (Effect) +import Test.UpdatedTests (updatedTests) + import Test.Data.List (testList) import Test.Data.List.Lazy (testListLazy) +import Test.Data.List.NonEmpty (testNonEmptyList) import Test.Data.List.Partial (testListPartial) import Test.Data.List.ZipList (testZipList) -import Test.Data.List.NonEmpty (testNonEmptyList) main :: Effect Unit main = do + --originalTests + updatedTests + pure unit + +originalTests :: Effect Unit +originalTests = do testList testListLazy testZipList testListPartial testNonEmptyList + -- Missing testLazyNonEmptyList \ No newline at end of file diff --git a/test/Test/UpdatedTests.purs b/test/Test/UpdatedTests.purs new file mode 100644 index 0000000..da53348 --- /dev/null +++ b/test/Test/UpdatedTests.purs @@ -0,0 +1,90 @@ +module Test.UpdatedTests(updatedTests) where + +import Prelude + +import Data.List as L +import Data.List.Lazy as LL +import Data.List.Lazy.NonEmpty as LNEL +import Data.List.NonEmpty as NEL +import Effect (Effect) +import Test.AllTests as T +import Test.Args.LazyList as LLA +import Test.Args.LazyNonEmptyList as LNELA +import Test.Args.List as LA +import Test.Args.NonEmptyList as NELA + +{- +--- Next steps: + +rebase +- fix "an list" -> "a list" + - or even "a collection" +- upgrade to assertEqual + + +-} + +updatedTests :: Effect Unit +updatedTests = do + testBasicList + -- testNonEmptyList + -- testLazyList + --testLazyNonEmptyList -- Lots of stuff to fix here + + -- Just using original ZipList tests + {- + Todo + This is a wrapper on Lazy list. Should this be clarified in + the name, and should there be a zip wrapper for non-lazy lists? + Also, it doesn't seem like all instances are tested. Should + testing be expanded? + -} + --testZipList + + -- Just using original ListPartial tests + -- testListPartial + +testBasicList :: Effect Unit +testBasicList = do + + T.printCollectionType "Basic List" + + T.testCommon LA.common + T.testCommonDiffEmptiability T.RunAll LA.commonDiffEmptiability + T.testOnlyCanEmpty T.SkipBrokenStrictCanEmpty LA.onlyCanEmpty LA.common LA.commonDiffEmptiability + T.testOnlyStrict LA.onlyStrict + T.testOnlyStrictCanEmpty LA.onlyStrictCanEmpty + +testNonEmptyList :: Effect Unit +testNonEmptyList = do + + T.printCollectionType "NonEmpty List" + + T.testCommon NELA.common + T.testCommonDiffEmptiability T.SkipBrokenStrictNonEmpty NELA.commonDiffEmptiability + T.testOnlyNonEmpty NELA.onlyNonEmpty NELA.commonDiffEmptiability + T.testOnlyStrict NELA.onlyStrict + T.testOnlyStrictNonEmpty NELA.onlyStrictNonEmpty + +testLazyList :: Effect Unit +testLazyList = do + + T.testCommon LLA.common + T.testCommonDiffEmptiability T.SkipBrokenLazyCanEmpty LLA.commonDiffEmptiability + T.testOnlyCanEmpty T.SkipBrokenLazyCanEmpty LLA.onlyCanEmpty LLA.common LLA.commonDiffEmptiability + T.testOnlyLazy LLA.onlyLazy LLA.common + T.testOnlyLazyCanEmpty LLA.onlyLazyCanEmpty + + +testLazyNonEmptyList :: Effect Unit +testLazyNonEmptyList = do + + T.printCollectionType "Lazy NonEmpty List" + + -- So much stuff is unsupported for this collection that it's not yet + -- worth using the assertSkip strategy + T.testCommon LNELA.common + T.testCommonDiffEmptiability T.RunAll LNELA.commonDiffEmptiability + T.testOnlyNonEmpty LNELA.onlyNonEmpty LNELA.commonDiffEmptiability + T.testOnlyLazy LNELA.onlyLazy LNELA.common + T.testOnlyLazyNonEmpty LNELA.onlyLazyNonEmpty \ No newline at end of file