diff --git a/src/Data/List.purs b/src/Data/List.purs index db9c1cd..89bde1f 100644 --- a/src/Data/List.purs +++ b/src/Data/List.purs @@ -95,6 +95,13 @@ module Data.List , foldM , module Exports + + -- additions + , appendFoldable + + , cons' + , snoc' + ) where import Prelude @@ -117,8 +124,20 @@ 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) import Prim.TypeError (class Warn, Text) + +---------- Additions + +appendFoldable :: forall t a. Foldable t => List a -> t a -> List a +appendFoldable _ _ = unsafeCrashWith "todo appendFoldable for Basic List" + +cons' :: forall a. a -> NEL.NonEmptyList a -> List a +cons' _ _ = unsafeCrashWith "todo cons' for Basic List" +snoc' :: forall a. NEL.NonEmptyList a -> a -> List a +snoc' _ _ = unsafeCrashWith "todo snoc' for Basic List" + -- | Convert a list into any unfoldable structure. -- | -- | Running time: `O(n)` diff --git a/src/Data/List/Lazy.purs b/src/Data/List/Lazy.purs index 8821753..fe3dc4a 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,19 @@ module Data.List.Lazy , scanlLazy , module Exports + + -- additions + , appendFoldable + , someRec + , sort + , sortBy + + , cons' + , dropEnd + , groupAllBy + , snoc' + , manyRec + ) where import Prelude @@ -102,6 +117,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 +131,29 @@ 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. Ord a => (a -> a -> Boolean) -> 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" -- | Convert a list into any unfoldable structure. -- | @@ -506,6 +545,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 +566,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 +611,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..ce2ca21 100644 --- a/src/Data/List/Lazy/NonEmpty.purs +++ b/src/Data/List/Lazy/NonEmpty.purs @@ -13,21 +13,270 @@ 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 + ) 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. Ord a => (a -> a -> Boolean) -> 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 -> NonEmptyList 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" + +----------- toUnfoldable :: forall f. Unfoldable f => NonEmptyList ~> f toUnfoldable = @@ -75,9 +324,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/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 01c3db7..628d5ca 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,22 +62,43 @@ 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.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 Partial.Unsafe (unsafeCrashWith, unsafePartial) 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 @@ -84,6 +107,38 @@ import Data.Traversable (scanl, scanr) as Exports 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 (NonEmptyList 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 +188,14 @@ 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. +-- Todo, rewrite this without unsafe workaround (if necessary) +range :: Int -> Int -> NonEmptyList Int +range start end = unsafePartial fromJust $ fromList $ L.range start end + cons :: forall a. a -> NonEmptyList a -> NonEmptyList a cons y (NonEmptyList (x :| xs)) = NonEmptyList (y :| x : xs) @@ -250,6 +313,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 @@ -319,3 +385,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/Common.purs b/test/Test/Common.purs new file mode 100644 index 0000000..d12bcf8 --- /dev/null +++ b/test/Test/Common.purs @@ -0,0 +1,671 @@ +module Test.Common where + +import Prelude + +import Control.Alt (class Alt, (<|>)) +import Control.Alternative (class Alternative) +import Control.Extend (class Extend, (<<=)) +import Control.Lazy (class Lazy) +import Control.Monad.Rec.Class (class MonadRec) +import Data.Array as Array +import Data.Eq (class Eq1, eq1) +import Data.Foldable (class Foldable, foldMap, foldl, sum) +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) +import Data.Traversable (class Traversable, traverse) +import Data.TraversableWithIndex (class TraversableWithIndex, traverseWithIndex) +import Data.Tuple (Tuple(..)) +import Data.Unfoldable (class Unfoldable) +import Data.Unfoldable1 (class Unfoldable1, unfoldr1) +import Effect (Effect) +import Effect.Console (log) +import Partial.Unsafe (unsafePartial) +import Test.Assert (assert, assertEqual, assertEqual') + +{- +This 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 + +assertSkipHelper :: SkipBroken -> Array SkipBroken -> (_ -> Boolean) -> Effect Unit +assertSkipHelper skip arr f = + case Array.elem skip arr of + true -> log "...skipped" + false -> assert $ f unit + +printCollectionType :: String -> Effect Unit +printCollectionType str = do + log "--------------------------------" + log str + log "--------------------------------" + +printTestType :: String -> Effect Unit +printTestType str = do + log $ "---- " <> str <> " Tests ----" + +class ( + 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 +) <= Common c where + 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 + 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) + + + +-- Don't know how to define this in Test.Data.List +-- Wrapping is tricky. +instance commonList :: Common L.List where + makeCollection = L.fromFoldable + + concat = L.concat + concatMap = L.concatMap + cons = L.Cons -- Should basic list have a cons function wrapping the Cons constructor? + elemIndex = L.elemIndex + elemLastIndex = L.elemLastIndex + findIndex = L.findIndex + findLastIndex = L.findLastIndex + foldM = L.foldM + index = L.index + intersect = L.intersect + intersectBy = L.intersectBy + length = L.length + nubEq = L.nubEq + nubByEq = L.nubByEq + range = L.range + reverse = L.reverse + singleton = L.singleton + snoc = L.snoc + toUnfoldable = L.toUnfoldable + union = L.union + unionBy = L.unionBy + unzip = L.unzip + zip = L.zip + zipWith = L.zipWith + zipWithA = L.zipWithA + + appendFoldable = L.appendFoldable + insert = L.insert + insertBy = L.insertBy + nub = L.nub + nubBy = L.nubBy + some = L.some + someRec = L.someRec + sort = L.sort + sortBy = L.sortBy + transpose = L.transpose + +instance commonNonEmptyList :: Common NEL.NonEmptyList where + makeCollection = unsafePartial fromJust <<< NEL.fromFoldable + + concat = NEL.concat + concatMap = NEL.concatMap + cons = NEL.cons + elemIndex = NEL.elemIndex + elemLastIndex = NEL.elemLastIndex + findIndex = NEL.findIndex + findLastIndex = NEL.findLastIndex + foldM = NEL.foldM + index = NEL.index + intersect = NEL.intersect + intersectBy = NEL.intersectBy + length = NEL.length + nubEq = NEL.nubEq + nubByEq = NEL.nubByEq + range = NEL.range + reverse = NEL.reverse + singleton = NEL.singleton + snoc = NEL.snoc + toUnfoldable = NEL.toUnfoldable + union = NEL.union + unionBy = NEL.unionBy + unzip = NEL.unzip + zip = NEL.zip + zipWith = NEL.zipWith + zipWithA = NEL.zipWithA + + appendFoldable = NEL.appendFoldable + insert = NEL.insert + insertBy = NEL.insertBy + nub = NEL.nub + nubBy = NEL.nubBy + some = NEL.some + someRec = NEL.someRec + sort = NEL.sort + sortBy = NEL.sortBy + transpose = NEL.transpose + +instance commonLazyList :: Common LL.List where + makeCollection = LL.fromFoldable + + concat = LL.concat + concatMap = LL.concatMap + cons = LL.cons + elemIndex = LL.elemIndex + elemLastIndex = LL.elemLastIndex + findIndex = LL.findIndex + findLastIndex = LL.findLastIndex + foldM = LL.foldM + index = LL.index + intersect = LL.intersect + intersectBy = LL.intersectBy + length = LL.length + nubEq = LL.nubEq + nubByEq = LL.nubByEq + range = LL.range + reverse = LL.reverse + singleton = LL.singleton + snoc = LL.snoc + toUnfoldable = LL.toUnfoldable + union = LL.union + unionBy = LL.unionBy + unzip = LL.unzip + zip = LL.zip + zipWith = LL.zipWith + zipWithA = LL.zipWithA + + appendFoldable = LL.appendFoldable + insert = LL.insert + insertBy = LL.insertBy + nub = LL.nub + nubBy = LL.nubBy + some = LL.some + someRec = LL.someRec + sort = LL.sort + sortBy = LL.sortBy + transpose = LL.transpose + +instance commonLazyNonEmptyList :: Common LNEL.NonEmptyList where + makeCollection = unsafePartial fromJust <<< LNEL.fromFoldable + + concat = LNEL.concat + concatMap = LNEL.concatMap + cons = LNEL.cons + elemIndex = LNEL.elemIndex + elemLastIndex = LNEL.elemLastIndex + findIndex = LNEL.findIndex + findLastIndex = LNEL.findLastIndex + foldM = LNEL.foldM + index = LNEL.index + intersect = LNEL.intersect + intersectBy = LNEL.intersectBy + length = LNEL.length + nubEq = LNEL.nubEq + nubByEq = LNEL.nubByEq + range = LNEL.range + reverse = LNEL.reverse + singleton = LNEL.singleton + snoc = LNEL.snoc + toUnfoldable = LNEL.toUnfoldable + union = LNEL.union + unionBy = LNEL.unionBy + unzip = LNEL.unzip + zip = LNEL.zip + zipWith = LNEL.zipWith + zipWithA = LNEL.zipWithA + + appendFoldable = LNEL.appendFoldable + insert = LNEL.insert + insertBy = LNEL.insertBy + nub = LNEL.nub + nubBy = LNEL.nubBy + some = LNEL.some + someRec = LNEL.someRec + sort = LNEL.sort + sortBy = LNEL.sortBy + transpose = LNEL.transpose + +testCommon :: forall c. + Common c => + Eq (c String) => + Eq (c (Tuple Int String)) => + Eq (c (c String)) => + Eq (c (Array Int)) => + Show (c String) => + Show (c (Tuple Int String)) => + Show (c (c String)) => + Show (c (Array Int)) => + c Int -> Effect Unit +-- Would likely be better to pass a proxy type +testCommon _ = do + let + l :: forall f a. Foldable f => f a -> c a + l = makeCollection + + rg :: Int -> Int -> c Int + rg = range + + bigCollection :: c _ + bigCollection = range 1 100000 + + printTestType "Common" + + -- Duplicating this test out of alphabetical order, 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 + -- Todo - Don't know in what situations this is different than append + 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 + log "Apply's apply (<*>) should have cartesian product behavior for non-zippy collections" + log "... skipped" + -- Todo - make these consistent and also double-check for arrays + -- can-empty behavior + -- assertEqual { actual: makeCollection [mul 10, mul 100] <*> l [1, 2, 3], expected: l [10, 20, 30, 100, 200, 300] } + -- NonEmpty behavior + -- assertEqual { actual: makeCollection [mul 10, mul 100] <*> l [1, 2, 3], expected: l [10, 100, 20, 200, 30, 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 + -- 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 + -- These are just the pre-existing tests. They could be more comprehensive. + + log "foldl should be stack-safe" + void $ pure $ foldl (+) 0 bigCollection + + log "foldMap should be stack-safe" + void $ pure $ foldMap Additive bigCollection + + log "foldMap should be left-to-right" + assertEqual { actual: foldMap show $ rg 1 5, expected: "12345" } + + -- 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 + -- Todo - Existing tests, opportunities for improvement + + log "foldlWithIndex should be correct" + assertEqual { actual: foldlWithIndex (\i b _ -> i + b) 0 $ rg 0 10000, expected: 50005000 } + + log "foldlWithIndex should be stack-safe" + void $ pure $ foldlWithIndex (\i b _ -> i + b) 0 bigCollection + + log "foldrWithIndex should be correct" + assertEqual { actual: foldrWithIndex (\i _ b -> i + b) 0 $ rg 0 10000, expected: 50005000 } + + log "foldrWithIndex should be stack-safe" + void $ pure $ foldrWithIndex (\i _ b -> i + b) 0 bigCollection + + log "foldMapWithIndex should be stack-safe" + void $ pure $ foldMapWithIndex (\i _ -> Additive i) bigCollection + + log "foldMapWithIndex should be left-to-right" + assertEqual { actual: foldMapWithIndex (\i _ -> show i) (l [0, 0, 0]), expected: "012" } + + -- Functor + -- map :: forall a b. (a -> b) -> f a -> f b + + log "map should maintain order" + assertEqual { actual: rg 1 5, expected: map identity $ rg 1 5 } + + log "map should be stack-safe" + void $ pure $ map identity bigCollection + -- Todo - The below test also performs the same stack-safety check + + log "map should be correct" + assertEqual { actual: rg 1 100000, expected: map (_ + 1) $ rg 0 99999 } + + + -- FunctorWithIndex + -- mapWithIndex :: forall a b. (i -> a -> b) -> f a -> f b + -- Todo - improve pre-existing + + log "mapWithIndex should take a collection of values and apply a function which also takes the index into account" + assertEqual { actual: mapWithIndex add $ l [0, 1, 2, 3], expected: l [0, 2, 4, 6] } + + -- Monad + -- Indicates Applicative and Bind, which are already tested + + -- Ord + -- compare :: a -> a -> Ordering + -- Todo - add tests + + -- Ord1 + -- compare1 :: forall a. Ord a => f a -> f a -> Ordering + -- Todo - add tests + + -- Semigroup + -- append :: a -> a -> a + + log "append should concatenate two collections" + assertEqual { actual: l [1, 2] <> l [3, 4], expected: l [1, 2, 3, 4] } + + log "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) + -- Todo - improve pre-existing tests + -- Todo - add sequence test + + log "traverse should be stack-safe" + assertEqual { actual: traverse Just bigCollection, expected: Just bigCollection } + + -- TraversableWithIndex + -- traverseWithIndex :: forall a b m. Applicative m => (i -> a -> m b) -> t a -> m (t b) + + log "traverseWithIndex should be stack-safe" + assertEqual { actual: traverseWithIndex (const Just) bigCollection, expected: Just bigCollection } + + log "traverseWithIndex should be correct" + assertEqual { actual: traverseWithIndex (\i a -> Just $ i + a) (l [2, 2, 2]), expected: Just $ l [2, 3, 4] } + + -- 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 "unfoldr1 should maintain order" + assertEqual { actual: rg 1 5, expected: unfoldr1 step1 1 } + + -- =========== Functions =========== + + -- Todo - split + -- log "catMaybe should take a collection of Maybe values and throw out Nothings" + -- assertEqual { actual: catMaybes (l [Nothing, Just 2, Nothing, Just 4]), expected: l [2, 4] } + + log "concat should join a collection of collections" + assertEqual { actual: concat $ l [l [1, 2], l [3, 4]], expected: l [1, 2, 3, 4] } + + 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] } + + 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 } + + 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 } + + -- Todo split + -- log "filter should remove items that don't match a predicate" + -- assertEqual { actual: filter odd $ range 0 10, expected: l [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) $ range 0 10, expected: Just $ l [1, 3, 5, 7, 9] } + --assertEqual { actual: filterM (const Nothing) $ rg 0 10, 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" + assertEqual { actual: foldM (\x y -> Just $ x + y) 0 $ rg 1 10, expected: Just 55 } + assertEqual { actual: foldM (\_ _ -> Nothing) 0 $ rg 1 10, 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 } + + -- todo split + -- 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 } + + 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 + + -- todo split + -- 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 } + + 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 "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] } + + 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 toUnfoldable + + 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 [1, 1, 2, 3]) $ l [2, 3, 4], expected: l [1, 1, 2, 3, 4] } + + log "unionBy should produce the union of two collections using the specified equality relation" + assertEqual { actual: unionBy (\_ y -> y < 5) (l [1, 2, 3]) $ l [2, 3, 4, 5, 6], expected: l [1, 2, 3, 5, 6] } + + 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"] } + + 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"]] } + + log "zipWithA should use the specified function to zip two collections together" + assertEqual { actual: zipWithA (\x y -> Just $ Tuple x y) (l [1, 2, 3]) $ l ["a", "b", "c"], expected: Just $ l [Tuple 1 "a", Tuple 2 "b", Tuple 3 "c"] } + + {- + New stuff + Todo: + -- convert to assertEqual + -- sort into above + -} + + -- appendFoldable :: forall t a. Foldable t => c a -> t a -> c a + -- todo + + {- + -- Todo - clean these up + + log "insert should add an item at the appropriate place in a sorted list" + assert $ insert 2 (l [1, 1, 3]) == l [1, 1, 2, 3] + assert $ insert 4 (l [1, 2, 3]) == l [1, 2, 3, 4] + assert $ insert 0 (l [1, 2, 3]) == l [0, 1, 2, 3] + + log "insertBy should add an item at the appropriate place in a sorted list using the specified comparison" + assert $ insertBy (flip compare) 4 (l [1, 2, 3]) == l [4, 1, 2, 3] + assert $ insertBy (flip compare) 0 (l [1, 2, 3]) == l [1, 2, 3, 0] + + -- nub :: forall a. Ord a => c a -> c a + -- nubBy :: forall a. (a -> a -> Ordering) -> c a -> c a + + log "nub should remove duplicate elements from the list, keeping the first occurrence" + assert $ nub (l [1, 2, 2, 3, 4, 1]) == l [1, 2, 3, 4] + + log "nubBy should remove duplicate items from the list using a supplied predicate" + assert $ nubBy (compare `on` Array.length) (l [[1],[2],[3,4]]) == l [[1],[3,4]] + -} + + + {- + -- replicate :: forall a. Int -> a -> c a + log "unfoldable replicate should be stack-safe" + void $ pure $ length $ replicate 100000 1 + + log "replicate should produce an list containing an item a specified number of times" + assert $ replicate 3 true == l [true, true, true] + assert $ replicate 1 "foo" == l ["foo"] + assert $ replicate 0 "foo" == l [] + assert $ replicate (-1) "foo" == l [] + + log "replicateA should perform the monadic action the correct number of times" + assert $ replicateA 3 (Just 1) == Just (l [1, 1, 1]) + assert $ replicateA 1 (Just 1) == Just (l [1]) + assert $ replicateA 0 (Just 1) == Just (l []) + assert $ replicateA (-1) (Just 1) == Just (l []) + -} + + + + + -- replicateM :: forall m a. Monad m => Int -> m a -> m (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) + diff --git a/test/Test/CommonDiffEmptiability.purs b/test/Test/CommonDiffEmptiability.purs new file mode 100644 index 0000000..bd37bba --- /dev/null +++ b/test/Test/CommonDiffEmptiability.purs @@ -0,0 +1,299 @@ +module Test.CommonDiffEmptiability where + +import Prelude + +import Data.Foldable (class Foldable) +import Data.Function (on) +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 Effect (Effect) +import Effect.Console (log) +import Partial.Unsafe (unsafePartial) +import Test.Assert (assert) +import Test.Common (class Common, SkipBroken(..), assertSkipHelper, printTestType, makeCollection, range) + +{- +This is for testing common functions that have slightly different +signatures depending on whether the collection may be empty or not. +For example: + CanEmpty (as `c`): + drop :: forall a. Int -> c a -> c a + fromFoldable :: forall f. Foldable f => f ~> c + group :: forall a. Eq a => c a -> c (nonEmpty a) + head :: forall a. c a -> Maybe a + NonEmpty (as `c`): + drop :: forall a. Int -> c a -> canEmpty a + fromFoldable :: forall f a. Foldable f => f a -> Maybe (c a) + group :: forall a. Eq a => c a -> c (c a) + head :: forall a. c a -> a + +These are consolidated by providing different type constructors to the typeclass instances. + +This generally works, but cannot be done if `Maybe` is present in one of the versions. +So functions like `fromFoldable` and `head` must be tested elswhere with some duplication. +The original plan was to pass another function with the same kind signature as `Maybe`, +such as: + type Id x = x +But creating an "identity" type alias doesn't work because: + - First-class type families are required: + - https://stackoverflow.com/questions/63865620/can-haskell-type-synonyms-be-used-as-type-constructors + - Typeclasses only match on type constructors and not any arbritrary + type-level function with the same kind signature. + - https://old.reddit.com/r/haskell/comments/26dshj/why_doesnt_haskell_allow_type_aliases_in_the/ +-} + + +class ( + Eq (c Int) +) <= CommonDiffEmptiability c cInverse canEmpty nonEmpty cPattern | c -> cInverse canEmpty nonEmpty cPattern where + + toCanEmpty :: forall a. c a -> canEmpty a + toNonEmpty :: forall a. c a -> nonEmpty 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 + -- There's a pending PR to update this signature + -- groupAllBy :: forall a. (a -> a -> Ordering) -> c a -> c (nonEmpty a) + groupAllBy :: forall a. Ord a => (a -> a -> Boolean) -> 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) + +instance commonDiffEmptiabilityCanEmptyList :: CommonDiffEmptiability L.List NEL.NonEmptyList L.List NEL.NonEmptyList L.Pattern where + + toCanEmpty = identity + toNonEmpty = unsafePartial fromJust <<< NEL.fromList + + catMaybes = L.catMaybes + drop = L.drop + dropWhile = L.dropWhile + filter = L.filter + filterM = L.filterM + group = L.group + groupAll = L.groupAll + groupBy = L.groupBy + mapMaybe = L.mapMaybe + partition = L.partition + span = L.span + take = L.take + takeEnd = L.takeEnd + takeWhile = L.takeWhile + + cons' = L.cons' + delete = L.delete + deleteBy = L.deleteBy + difference = L.difference + dropEnd = L.dropEnd + groupAllBy = L.groupAllBy + pattern = L.Pattern + slice = L.slice + snoc' = L.snoc' + stripPrefix = L.stripPrefix + +instance commonDiffEmptiabilityNonEmptyList :: CommonDiffEmptiability NEL.NonEmptyList L.List L.List NEL.NonEmptyList NEL.Pattern where + + toCanEmpty = NEL.toList + toNonEmpty = identity + + catMaybes = NEL.catMaybes + drop = NEL.drop + dropWhile = NEL.dropWhile + filter = NEL.filter + filterM = NEL.filterM + group = NEL.group + groupAll = NEL.groupAll + groupBy = NEL.groupBy + mapMaybe = NEL.mapMaybe + partition = NEL.partition + span = NEL.span + take = NEL.take + takeEnd = NEL.takeEnd + takeWhile = NEL.takeWhile + + cons' = NEL.cons' + delete = NEL.delete + deleteBy = NEL.deleteBy + difference = NEL.difference + dropEnd = NEL.dropEnd + groupAllBy = NEL.groupAllBy + pattern = NEL.Pattern + slice = NEL.slice + snoc' = NEL.snoc' + stripPrefix = NEL.stripPrefix + +instance commonDiffEmptiabilityCanEmptyLazyList :: CommonDiffEmptiability LL.List LNEL.NonEmptyList LL.List LNEL.NonEmptyList LL.Pattern where + + toCanEmpty = identity + toNonEmpty = unsafePartial fromJust <<< LNEL.fromList + + catMaybes = LL.catMaybes + drop = LL.drop + dropWhile = LL.dropWhile + filter = LL.filter + filterM = LL.filterM + group = LL.group + groupAll = LL.groupAll + groupBy = LL.groupBy + mapMaybe = LL.mapMaybe + partition = LL.partition + span = LL.span + take = LL.take + takeEnd = LL.takeEnd + takeWhile = LL.takeWhile + + cons' = LL.cons' + delete = LL.delete + deleteBy = LL.deleteBy + difference = LL.difference + dropEnd = LL.dropEnd + groupAllBy = LL.groupAllBy + pattern = LL.Pattern + slice = LL.slice + snoc' = LL.snoc' + stripPrefix = LL.stripPrefix + +instance commonDiffEmptiabilityLazyNonEmptyList :: CommonDiffEmptiability LNEL.NonEmptyList LL.List LL.List LNEL.NonEmptyList LNEL.Pattern where + + toCanEmpty = LNEL.toList + toNonEmpty = identity + + catMaybes = LNEL.catMaybes + drop = LNEL.drop + dropWhile = LNEL.dropWhile + filter = LNEL.filter + filterM = LNEL.filterM + group = LNEL.group + groupAll = LNEL.groupAll + groupBy = LNEL.groupBy + mapMaybe = LNEL.mapMaybe + partition = LNEL.partition + span = LNEL.span + take = LNEL.take + takeEnd = LNEL.takeEnd + takeWhile = LNEL.takeWhile + + cons' = LNEL.cons' + delete = LNEL.delete + deleteBy = LNEL.deleteBy + difference = LNEL.difference + dropEnd = LNEL.dropEnd + groupAllBy = LNEL.groupAllBy + pattern = LNEL.Pattern + slice = LNEL.slice + snoc' = LNEL.snoc' + stripPrefix = LNEL.stripPrefix + +testCommonDiffEmptiability :: forall c cInverse canEmpty nonEmpty cPattern. + Common c => + CommonDiffEmptiability c cInverse canEmpty nonEmpty cPattern => + Eq (c (nonEmpty Int)) => + Eq (canEmpty Int) => + SkipBroken -> c Int -> canEmpty Int -> nonEmpty Int -> Effect Unit +testCommonDiffEmptiability skip _ nil _ = do + let + l :: forall f a. Foldable f => f a -> c a + l = makeCollection + + cel :: forall f a. Foldable f => f a -> canEmpty a + cel = toCanEmpty <<< l + + nel :: forall f a. Foldable f => f a -> nonEmpty a + nel = toNonEmpty <<< l + + assertSkip :: Array SkipBroken -> (_ -> Boolean) -> Effect Unit + assertSkip = assertSkipHelper skip + + printTestType "Common (where signatures differ based on emptiability)" + + --catMaybes :: forall a. c (Maybe a) -> c a + -- todo + + log "drop should remove the specified number of items from the front of an list" + assert $ (drop 1 (l [1, 2, 3])) == cel [2, 3] + assert $ (drop (-1) (l [1, 2, 3])) == cel [1, 2, 3] + + log "dropWhile should remove all values that match a predicate from the front of an list" + assert $ (dropWhile (_ /= 1) (l [1, 2, 3])) == cel [1, 2, 3] + assert $ (dropWhile (_ /= 2) (l [1, 2, 3])) == cel [2, 3] + --assert $ (dropWhile (_ /= 1) nil) == nil + + --filter :: forall a. (a -> Boolean) -> c a -> c a + -- todo + + --filterM :: forall m a. Monad m => (a -> m Boolean) -> c a -> m (c a) + -- todo + + log "group should group consecutive equal elements into lists" + assert $ group (l [1, 2, 2, 3, 3, 3, 1]) == l [nel [1], nel [2, 2], nel [3, 3, 3], nel [1]] + + log "groupAll should group equal elements into lists" + assertSkip [SkipBrokenLazyCanEmpty] + \_ -> groupAll (l [1, 2, 2, 3, 3, 3, 1]) == l [nel [1, 1], nel [2, 2], nel [3, 3, 3]] + --assert $ groupAll (l [1, 2, 2, 3, 3, 3, 1]) == l [nel [1, 1], nel [2, 2], nel [3, 3, 3]] + + log "groupBy should group consecutive equal elements into lists based on an equivalence relation" + assert $ groupBy (eq `on` (_ `mod` 10)) (l [1, 2, 12, 3, 13, 23, 11]) == l [nel [1], nel [2, 12], nel [3, 13, 23], nel [11]] + + -- todo - wait for this to be reworked + -- log "groupAllBy should group equal elements into lists based on an comparison function" + --assert $ groupAllBy (compare `on` mod 10) (l [1, 2, 12, 3, 13, 23, 11]) == l [nel [1, 11], nel [2, 12], nel [3, 13, 23]] + + log "mapMaybe should transform every item in an list, throwing out Nothing values" + assert $ mapMaybe (\x -> if x /= 0 then Just x else Nothing) (l [0, 1, 0, 0, 2, 3]) == cel [1, 2, 3] + + 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]) + assert $ partitioned.yes == cel [5, 3, 4] + assert $ partitioned.no == cel [1, 2] + + log "span should split an list in two based on a predicate" + let spanResult = span (_ < 4) (l [1, 2, 3, 4, 5, 6, 7]) + assert $ spanResult.init == cel [1, 2, 3] + assert $ spanResult.rest == cel [4, 5, 6, 7] + + log "take should keep the specified number of items from the front of an list, discarding the rest" + assert $ (take 1 (l [1, 2, 3])) == cel [1] + assert $ (take 2 (l [1, 2, 3])) == cel [1, 2] + --assert $ (take 1 nil) == nil + assert $ (take 0 (l [1, 2])) == nil + assert $ (take (-1) (l [1, 2])) == nil + + log "takeEnd should keep the specified number of items from the end of an list, discarding the rest" + assertSkip [SkipBrokenLazyCanEmpty] + \_ -> (takeEnd 1 (l [1, 2, 3])) == cel [3] + assertSkip [SkipBrokenLazyCanEmpty] + \_ -> (takeEnd 2 (l [1, 2, 3])) == cel [2, 3] + assertSkip [SkipBrokenLazyCanEmpty] + \_ -> (takeEnd 2 (l [1])) == cel [1] + + --assert $ (takeEnd 1 (l [1, 2, 3])) == cel [3] + --assert $ (takeEnd 2 (l [1, 2, 3])) == cel [2, 3] + ----assert $ (takeEnd 1 nil) == nil + --assert $ (takeEnd 2 (l [1])) == cel [1] + + log "takeWhile should keep all values that match a predicate from the front of an list" + assert $ (takeWhile (_ /= 2) (l [1, 2, 3])) == cel [1] + assert $ (takeWhile (_ /= 3) (l [1, 2, 3])) == cel [1, 2] + --assert $ (takeWhile (_ /= 1) nil) == nil 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/NoOverlap.purs b/test/Test/NoOverlap.purs new file mode 100644 index 0000000..f563fa3 --- /dev/null +++ b/test/Test/NoOverlap.purs @@ -0,0 +1,117 @@ +module Test.NoOverlap where + +import Prelude + +import Effect (Effect) + +import Data.Foldable (class Foldable) +import Data.List as L +import Data.List.NonEmpty as NEL +import Data.List.Lazy as LL +import Data.List.Lazy.NonEmpty as LNEL +import Data.Maybe (Maybe(..)) + +import Test.Common (printTestType, makeCollection) + +import Effect.Console (log) +import Test.Assert (assert) + +{- +This file contains functions that cannot be tested generically. +-} + + +assertSkip :: (_ -> Boolean) -> Effect Unit +assertSkip _ = + log "...skipped" + +testOnlyStrictCanEmpty :: Effect Unit +testOnlyStrictCanEmpty = do + + let + l :: forall f a. Foldable f => f a -> L.List a + l = makeCollection + + printTestType "Only Strict canEmpty" + + -- Common function names, but different signatures + + log "deleteAt should remove an item at the specified index" + assert $ L.deleteAt 0 (l [1, 2, 3]) == Just (l [2, 3]) + assert $ L.deleteAt 1 (l [1, 2, 3]) == Just (l [1, 3]) + + -- Corner Cases + + -- Unique functions + + +testOnlyStrictNonEmpty :: Effect Unit +testOnlyStrictNonEmpty = do + + let + l :: forall f a. Foldable f => f a -> NEL.NonEmptyList a + l = makeCollection + + cel :: forall f a. Foldable f => f a -> L.List a + cel = makeCollection + + printTestType "Only Strict NonEmpty" + + -- Common function names, but different signatures + + log "deleteAt should remove an item at the specified index" + assertSkip \_ -> NEL.deleteAt 0 (l [1, 2, 3]) == Just (cel [2, 3]) + assertSkip \_ -> NEL.deleteAt 1 (l [1, 2, 3]) == Just (cel [1, 3]) + + -- Corner Cases + + -- Unique functions + + +testOnlyLazyCanEmpty :: Effect Unit +testOnlyLazyCanEmpty = do + + let + l :: forall f a. Foldable f => f a -> LL.List a + l = makeCollection + + printTestType "Only Lazy canEmpty" + + -- Common function names, but different signatures + + log "deleteAt should remove an item at the specified index" + assert $ LL.deleteAt 0 (l [1, 2, 3]) == l [2, 3] + assert $ LL.deleteAt 1 (l [1, 2, 3]) == l [1, 3] + + -- Corner Cases + + -- Unique functions + + -- replicate (specialized from Unfoldable's replicate) + -- replicateM (specialized from Unfoldable's replicateA) + + +testOnlyLazyNonEmpty :: Effect Unit +testOnlyLazyNonEmpty = do + + let + l :: forall f a. Foldable f => f a -> LNEL.NonEmptyList a + l = makeCollection + + cel :: forall f a. Foldable f => f a -> LL.List a + cel = makeCollection + + printTestType "Only Lazy NonEmpty" + + -- Common function names, but different signatures + + log "deleteAt should remove an item at the specified index" + assert $ LNEL.deleteAt 0 (l [1, 2, 3]) == cel [2, 3] + assert $ LNEL.deleteAt 1 (l [1, 2, 3]) == cel [1, 3] + + -- Corner Cases + + -- Unique functions + + -- replicate1 (specialized from Unfoldable1's replicate1) + -- replicate1M (specialized from Unfoldable1's replicate1A) \ No newline at end of file diff --git a/test/Test/OnlyCanEmpty.purs b/test/Test/OnlyCanEmpty.purs new file mode 100644 index 0000000..8f87b49 --- /dev/null +++ b/test/Test/OnlyCanEmpty.purs @@ -0,0 +1,183 @@ +module Test.OnlyCanEmpty where + +import Prelude + +import Control.Alternative (class Alternative) +import Control.Lazy (class Lazy) +import Control.Monad.Rec.Class (class MonadRec) +import Control.MonadPlus (class MonadPlus) +import Control.MonadZero (class MonadZero) +import Control.Plus (class Plus, empty) +import Data.Foldable (class Foldable) +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, isNothing) +import Data.Tuple (Tuple(..)) +import Data.Unfoldable (class Unfoldable, unfoldr) +import Effect (Effect) +import Effect.Console (log) +import Partial.Unsafe (unsafePartial) +import Test.Assert (assert) +import Test.Common (class Common, makeCollection, printTestType, range) + +class ( + Alternative c + , MonadPlus c + , MonadZero c + , Monoid (c Int) -- Monoid1? + , Plus c + , Unfoldable c +) <= OnlyCanEmpty c nonEmpty | c -> nonEmpty, nonEmpty -> c where + + makeNonEmptyCollection :: forall f a. Foldable f => f a -> nonEmpty 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 } + + -- These 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) + +instance onlyCanEmptyList :: OnlyCanEmpty L.List NEL.NonEmptyList where + + makeNonEmptyCollection = unsafePartial fromJust <<< NEL.fromFoldable + + fromFoldable = L.fromFoldable + head = L.head + init = L.init + last = L.last + tail = L.tail + uncons = L.uncons + + null = L.null + many = L.many + manyRec = L.manyRec + +instance onlyCanEmptyLazyList :: OnlyCanEmpty LL.List LNEL.NonEmptyList where + + makeNonEmptyCollection = unsafePartial fromJust <<< LNEL.fromFoldable + + fromFoldable = LL.fromFoldable + head = LL.head + init = LL.init + last = LL.last + tail = LL.tail + uncons = LL.uncons + + null = LL.null + many = LL.many + manyRec = LL.manyRec + + +testOnlyCanEmpty :: forall c nonEmpty. + Common c => + OnlyCanEmpty c nonEmpty => + Eq (c Int) => + Eq (c (nonEmpty Int)) => + c Int -> nonEmpty Int -> Effect Unit +testOnlyCanEmpty nil _ = do + let + l :: forall f a. Foldable f => f a -> c a + l = makeCollection + + nel :: forall f a. Foldable f => f a -> nonEmpty a + nel = makeNonEmptyCollection + + rg :: Int -> Int -> c Int + rg = range + + printTestType "Only canEmpty" + + -- ======= Typeclass tests ======== + + -- Alternative + -- applicative and plus + -- (f <|> g) <*> x == (f <*> x) <|> (g <*> x) + -- empty <*> f == empty + + -- MonadPlus + -- Additional law on MonadZero + -- (x <|> y) >>= f == (x >>= f) <|> (y >>= f) + + -- MonadZero + -- monad and alternative + -- empty >>= f = empty + + -- Monoid + -- mempty :: c + log "mempty should not change the collection it is appended to" + assert $ l [5] <> mempty == l [5] + log "mempty should be an empty collection" + assert $ l [] == (mempty :: c Int) + + -- Plus + -- empty :: forall a. c a + log "empty should create an empty collection" + assert $ l [] == (empty :: c Int) + + -- Unfoldable + -- unfoldr :: forall a b. (b -> Maybe (Tuple a b)) -> b -> c a + + log "unfoldr should maintain order" + let + step :: Int -> Maybe (Tuple Int Int) + step 6 = Nothing + step n = Just (Tuple n (n + 1)) + assert $ rg 1 5 == unfoldr step 1 + + + -- ======= Functions tests ======== + + --fromFoldable :: forall f. Foldable f => f ~> c + --already extensively checked in common tests + + -- These are the remaining functions that can't be deduplicated due to use of Maybe + + -- Todo - double-check the phrasing on these? Might be confusing to refer to a + -- non-empty canEmpty list. + + log "head should return a Just-NEL.NonEmptyListped first value of a non-empty list" + assert $ head (l [1, 2]) == Just 1 + + log "head should return Nothing for an empty list" + assert $ head nil == Nothing + + -- Todo - phrasing should be changed to note all but last (not all but first). + log "init should return a Just-NEL.NonEmptyListped list containing all the items in an list apart from the first for a non-empty list" + assert $ init (l [1, 2, 3]) == Just (l [1, 2]) + + log "init should return Nothing for an empty list" + assert $ init nil == Nothing + + + log "last should return a Just-NEL.NonEmptyListped last value of a non-empty list" + assert $ last (l [1, 2]) == Just 2 + + log "last should return Nothing for an empty list" + assert $ last nil == Nothing + + + log "tail should return a Just-NEL.NonEmptyListped list containing all the items in an list apart from the first for a non-empty list" + assert $ tail (l [1, 2, 3]) == Just (l [2, 3]) + + log "tail should return Nothing for an empty list" + assert $ tail nil == Nothing + + + log "uncons should return nothing when used on an empty list" + assert $ isNothing (uncons nil) + + log "uncons should split an list into a head and tail record when there is at least one item" + assert $ uncons (l [1]) == Just {head: 1, tail: l []} + assert $ uncons (l [1, 2, 3]) == Just {head: 1, tail: l [2, 3]} diff --git a/test/Test/OnlyLazy.purs b/test/Test/OnlyLazy.purs new file mode 100644 index 0000000..2b4a66c --- /dev/null +++ b/test/Test/OnlyLazy.purs @@ -0,0 +1,86 @@ +module Test.OnlyLazy where + +import Prelude + +import Data.Foldable (class Foldable) +import Data.Maybe (Maybe(..)) +import Control.Lazy (class Lazy) +import Effect (Effect) +import Effect.Console (log) +import Test.Assert (assert) + +import Test.Common (class Common, SkipBroken(..), assertSkipHelper, printTestType, makeCollection) + +import Data.List.Lazy as LL +import Data.List.Lazy.NonEmpty as LNEL + +{- +class ( + Lazy (c Int) -- missing from LazyNonEmptyList +) <= OnlyLazy c where +-} + +class OnlyLazy c where + +-- Same names, but different APIs (without Maybe) + alterAt :: forall a. Int -> (a -> Maybe a) -> c a -> c a + 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 + iterate :: forall a. (a -> a) -> a -> c a + repeat :: forall a. a -> c a + cycle :: forall a. c 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 + + +instance onlyLazyList :: OnlyLazy LL.List where + alterAt = LL.alterAt + insertAt = LL.insertAt + modifyAt = LL.modifyAt + updateAt = LL.updateAt + + iterate = LL.iterate + repeat = LL.repeat + cycle = LL.cycle + foldrLazy = LL.foldrLazy + scanlLazy = LL.scanlLazy + +instance onlyLazyNonEmptyList :: OnlyLazy LNEL.NonEmptyList where + alterAt = LNEL.alterAt + insertAt = LNEL.insertAt + modifyAt = LNEL.modifyAt + updateAt = LNEL.updateAt + + iterate = LNEL.iterate + repeat = LNEL.repeat + cycle = LNEL.cycle + foldrLazy = LNEL.foldrLazy + scanlLazy = LNEL.scanlLazy + +testOnlyLazy :: forall c. + Common c => + OnlyLazy c => + c Int -> Effect Unit +testOnlyLazy _ = do + let + l :: forall f a. Foldable f => f a -> c a + l = makeCollection + + printTestType "Only Lazy" + + log "insertAt should add an item at the specified index" + assert $ (insertAt 0 1 (l [2, 3])) == (l [1, 2, 3]) + assert $ (insertAt 1 1 (l [2, 3])) == (l [2, 1, 3]) + assert $ (insertAt 2 1 (l [2, 3])) == (l [2, 3, 1]) + + log "modifyAt should update an item at the specified index" + assert $ (modifyAt 0 (_ + 1) (l [1, 2, 3])) == (l [2, 2, 3]) + assert $ (modifyAt 1 (_ + 1) (l [1, 2, 3])) == (l [1, 3, 3]) + + log "updateAt should replace an item at the specified index" + assert $ (updateAt 0 9 (l [1, 2, 3])) == (l [9, 2, 3]) + assert $ (updateAt 1 9 (l [1, 2, 3])) == (l [1, 9, 3]) + diff --git a/test/Test/OnlyNonEmpty.purs b/test/Test/OnlyNonEmpty.purs new file mode 100644 index 0000000..cbd17cc --- /dev/null +++ b/test/Test/OnlyNonEmpty.purs @@ -0,0 +1,123 @@ +module Test.OnlyNonEmpty where + +import Prelude + +import Control.Comonad (class Comonad) +import Data.Foldable (class Foldable, foldMap, foldl) +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(..)) +import Data.Semigroup.Foldable (class Foldable1) +import Data.Semigroup.Traversable (class Traversable1) +import Effect (Effect) +import Effect.Console (log) +import Test.Assert (assert, assertEqual) +import Test.Common (class Common, SkipBroken(..), assertSkipHelper, printTestType, makeCollection) + +class ( + Comonad c + --, Foldable1 c -- missing from LazyNonEmptyList + --, Traversable1 c -- missing from LazyNonEmptyList +) <= OnlyNonEmpty c canEmpty | c -> canEmpty, canEmpty -> c where + + makeCanEmptyCollection :: forall f a. Foldable f => f a -> canEmpty a + + -- 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 } + + -- These are only available for NonEmpty collections + + fromList :: forall a. canEmpty a -> Maybe (c a) + toList :: c ~> canEmpty + +instance onlyNonEmptyList :: OnlyNonEmpty NEL.NonEmptyList L.List where + + makeCanEmptyCollection = L.fromFoldable + + fromFoldable = NEL.fromFoldable + head = NEL.head + init = NEL.init + last = NEL.last + tail = NEL.tail + uncons = NEL.uncons + + fromList = NEL.fromList + toList = NEL.toList + +instance onlyNonEmptyLazyList :: OnlyNonEmpty LNEL.NonEmptyList LL.List where + + makeCanEmptyCollection = LL.fromFoldable + + fromFoldable = LNEL.fromFoldable + head = LNEL.head + init = LNEL.init + last = LNEL.last + tail = LNEL.tail + uncons = LNEL.uncons + + fromList = LNEL.fromList + toList = LNEL.toList + +testOnlyNonEmpty :: forall c canEmpty. + Common c => + OnlyNonEmpty c canEmpty => + Eq (c Int) => + Eq (canEmpty Int) => + Show (canEmpty Int) => + c Int -> canEmpty Int -> Effect Unit +testOnlyNonEmpty _ _ = do + let + l :: forall f a. Foldable f => f a -> c a + l = makeCollection + + cel :: forall f a. Foldable f => f a -> canEmpty a + cel = makeCanEmptyCollection + + printTestType "Only nonEmpty" + + -- ======= Typeclass tests ======== + + -- Todo + + -- Comonad + -- Foldable1 + -- Traversable1 + + -- ======= Functions tests ======== + + 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 $ cel ([] :: _ Int), 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 "head should return a the first value" + assert $ head (l [1, 2]) == 1 + + log "init should return a canEmpty collection of all but the last value" + assert $ init (l [1, 2, 3]) == cel [1, 2] + + log "last should return the last value" + assert $ last (l [1, 2]) == 2 + + log "tail should return a canEmpty collection of all but the first value" + assert $ tail (l [1, 2, 3]) == cel [2, 3] + + log "uncons should split a collection into a record containing the first and remaining values" + assert $ uncons (l [1]) == {head: 1, tail: cel []} + assert $ uncons (l [1, 2, 3]) == {head: 1, tail: cel [2, 3]} diff --git a/test/Test/OnlyStrict.purs b/test/Test/OnlyStrict.purs new file mode 100644 index 0000000..553db0b --- /dev/null +++ b/test/Test/OnlyStrict.purs @@ -0,0 +1,79 @@ +module Test.OnlyStrict where + +import Prelude + +import Data.Foldable (class Foldable) +import Data.Maybe (Maybe(..)) +import Effect (Effect) +import Effect.Console (log) +import Test.Assert (assert) + +import Test.Common (class Common, SkipBroken(..), assertSkipHelper, printTestType, makeCollection) + +import Data.List as L +import Data.List.NonEmpty as NEL + +class OnlyStrict c where +-- Potentially just these functions: +-- Seems like they could also be common +{- +group' +mapWithIndex +sort +sortBy +unsnoc +-} + + -- Same names, but different APIs (with Maybe) + alterAt :: forall a. Int -> (a -> Maybe a) -> c a -> Maybe (c a) + 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) + + -- Strict only + -- recently fixed, so now common + --nub :: forall a. Ord a => c a -> c a + --nubBy :: forall a. (a -> a -> Ordering) -> c a -> c a + +instance onlyStrictList :: OnlyStrict L.List where + alterAt = L.alterAt + insertAt = L.insertAt + modifyAt = L.modifyAt + updateAt = L.updateAt + +instance onlyStrictNonEmptyList :: OnlyStrict NEL.NonEmptyList where + alterAt = NEL.alterAt + insertAt = NEL.insertAt + modifyAt = NEL.modifyAt + updateAt = NEL.updateAt + + + +testOnlyStrict :: forall c. + Common c => + OnlyStrict c => + c Int -> Effect Unit +testOnlyStrict _ = do + + let + l :: forall f a. Foldable f => f a -> c a + l = makeCollection + + printTestType "Only Strict" + + -- todo insertAt test + -- missing from original test suite + + -- todo modifyAt test + -- missing from original test suite + + log "updateAt should replace an item at the specified index" + assert $ (updateAt 0 9 (l [1, 2, 3])) == Just (l [9, 2, 3]) + assert $ (updateAt 1 9 (l [1, 2, 3])) == Just (l [1, 9, 3]) + + log "updateAt should return Nothing if the index is out of range" + assert $ (updateAt 5 9 (l [1, 2, 3])) == Nothing + + + + diff --git a/test/Test/UpdatedTests.purs b/test/Test/UpdatedTests.purs new file mode 100644 index 0000000..1708854 --- /dev/null +++ b/test/Test/UpdatedTests.purs @@ -0,0 +1,109 @@ +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.Common (testCommon, SkipBroken(..), printCollectionType) +import Test.CommonDiffEmptiability (testCommonDiffEmptiability) +import Test.NoOverlap (testOnlyLazyCanEmpty, testOnlyLazyNonEmpty, testOnlyStrictCanEmpty, testOnlyStrictNonEmpty) +import Test.OnlyCanEmpty (testOnlyCanEmpty) +import Test.OnlyLazy (testOnlyLazy) +import Test.OnlyNonEmpty (testOnlyNonEmpty) +import Test.OnlyStrict (testOnlyStrict) + + +{- +--- 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 + + printCollectionType "Basic List" + + testCommon nil + testCommonDiffEmptiability RunAll nil nil nonEmpty + testOnlyCanEmpty nil nonEmpty + testOnlyStrict nil + testOnlyStrictCanEmpty + +testNonEmptyList :: Effect Unit +testNonEmptyList = do + + printCollectionType "NonEmpty List" + + testCommon nonEmpty + testCommonDiffEmptiability RunAll nonEmpty nil nonEmpty + testOnlyNonEmpty nonEmpty nil + testOnlyStrict nonEmpty + testOnlyStrictNonEmpty + +testLazyList :: Effect Unit +testLazyList = do + + printCollectionType "Lazy List" + + testCommon lazyNil + testCommonDiffEmptiability SkipBrokenLazyCanEmpty lazyNil lazyNil lazyNonEmpty + testOnlyCanEmpty lazyNil lazyNonEmpty + testOnlyLazy lazyNil + testOnlyLazyCanEmpty + +testLazyNonEmptyList :: Effect Unit +testLazyNonEmptyList = do + + printCollectionType "Lazy NonEmpty List" + + -- So much stuff is unsupported for this collection that it's not yet + -- worth using the assertSkip strategy + testCommon lazyNonEmpty + testCommonDiffEmptiability RunAll lazyNonEmpty lazyNil lazyNonEmpty + testOnlyNonEmpty lazyNonEmpty lazyNil + testOnlyLazy lazyNonEmpty + testOnlyLazyNonEmpty + +-- nil is passed instead of a singleton, +-- because some of the functions use this +-- as a convenience value +nil :: L.List Int +nil = L.Nil + +lazyNil :: LL.List Int +lazyNil = LL.nil + +nonEmpty :: NEL.NonEmptyList Int +nonEmpty = NEL.singleton 1 + +lazyNonEmpty :: LNEL.NonEmptyList Int +lazyNonEmpty = LNEL.singleton 1 \ No newline at end of file