diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 099ba7b..5896755 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -5,6 +5,7 @@ module Data.List.NonEmpty , fromList , toList , singleton + , length , cons , snoc , head @@ -13,11 +14,46 @@ module Data.List.NonEmpty , init , uncons , unsnoc - , length + , (!!), index + , elemIndex + , elemLastIndex + , findIndex + , findLastIndex + , insertAt + , updateAt + , modifyAt + , reverse + , concat , concatMap + , filter + , filterM + , mapMaybe + , catMaybes , appendFoldable + , mapWithIndex , sort , sortBy + , take + , takeWhile + , drop + , dropWhile + , span + , group + , group' + , groupBy + , partition + , nub + , nubBy + , union + , unionBy + , intersect + , intersectBy + , zipWith + , zipWithA + , zip + , unzip + , foldM + , module Exports ) where import Prelude @@ -26,12 +62,50 @@ import Data.Foldable (class Foldable) import Data.List ((:)) import Data.List as L import Data.List.Types (NonEmptyList(..)) -import Data.Maybe (Maybe(..), maybe, fromMaybe, fromJust) +import Data.Maybe (Maybe(..), fromMaybe, maybe) import Data.NonEmpty ((:|)) import Data.NonEmpty as NE -import Data.Tuple (Tuple(..)) +import Data.Semigroup.Traversable (sequence1) +import Data.Tuple (Tuple(..), fst, snd) import Data.Unfoldable (class Unfoldable, unfoldr) -import Partial.Unsafe (unsafePartial) +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, traverse1, traverse1Default) as Exports +import Data.Traversable (scanl, scanr) as Exports + +-- | 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. +wrappedOperation + :: forall a b + . String + -> (L.List a -> L.List b) + -> NonEmptyList a + -> NonEmptyList b +wrappedOperation name f (NonEmptyList (x :| xs)) = + case f (x : xs) of + x' : xs' -> NonEmptyList (x' :| xs') + L.Nil -> unsafeCrashWith ("Impossible: empty list in NonEmptyList " <> name) + +-- | Like `wrappedOperation`, but for functions that operate on 2 lists. +wrappedOperation2 + :: forall a b c + . String + -> (L.List a -> L.List b -> L.List c) + -> NonEmptyList a + -> NonEmptyList b + -> NonEmptyList c +wrappedOperation2 name f (NonEmptyList (x :| xs)) (NonEmptyList (y :| ys)) = + case f (x : xs) (y : ys) of + x' : xs' -> NonEmptyList (x' :| xs') + L.Nil -> unsafeCrashWith ("Impossible: empty list in NonEmptyList " <> name) + +-- | Lifts a function that operates on a list to work on a NEL. This does not +-- | preserve the non-empty status of the result. +lift :: forall a b. (L.List a -> b) -> NonEmptyList a -> b +lift f (NonEmptyList (x :| xs)) = f (x : xs) toUnfoldable :: forall f. Unfoldable f => NonEmptyList ~> f toUnfoldable = @@ -79,6 +153,65 @@ unsnoc (NonEmptyList (x :| xs)) = case L.unsnoc xs of length :: forall a. NonEmptyList a -> Int length (NonEmptyList (x :| xs)) = 1 + L.length xs +index :: forall a. NonEmptyList a -> Int -> Maybe a +index (NonEmptyList (x :| xs)) i + | i == 0 = Just x + | otherwise = L.index xs (i - 1) + +infixl 8 index as !! + +elemIndex :: forall a. Eq a => a -> NonEmptyList a -> Maybe Int +elemIndex x = findIndex (_ == x) + +elemLastIndex :: forall a. Eq a => a -> NonEmptyList a -> Maybe Int +elemLastIndex x = findLastIndex (_ == x) + +findIndex :: forall a. (a -> Boolean) -> NonEmptyList a -> Maybe Int +findIndex f (NonEmptyList (x :| xs)) + | f x = Just 0 + | otherwise = (_ + 1) <$> L.findIndex f xs + +findLastIndex :: forall a. (a -> Boolean) -> NonEmptyList a -> Maybe Int +findLastIndex f (NonEmptyList (x :| xs)) = + case L.findLastIndex f xs of + Just i -> Just (i + 1) + Nothing + | f x -> Just 0 + | otherwise -> Nothing + +insertAt :: forall a. Int -> a -> NonEmptyList a -> Maybe (NonEmptyList a) +insertAt i a (NonEmptyList (x :| xs)) + | i == 0 = Just (NonEmptyList (a :| x : xs)) + | otherwise = NonEmptyList <<< (x :| _) <$> L.insertAt (i - 1) a xs + +updateAt :: forall a. Int -> a -> NonEmptyList a -> Maybe (NonEmptyList a) +updateAt i a (NonEmptyList (x :| xs)) + | i == 0 = Just (NonEmptyList (a :| xs)) + | otherwise = NonEmptyList <<< (x :| _) <$> L.updateAt (i - 1) a xs + +modifyAt :: forall a. Int -> (a -> a) -> NonEmptyList a -> Maybe (NonEmptyList a) +modifyAt i f (NonEmptyList (x :| xs)) + | i == 0 = Just (NonEmptyList (f x :| xs)) + | otherwise = NonEmptyList <<< (x :| _) <$> L.modifyAt (i - 1) f xs + +reverse :: forall a. NonEmptyList a -> NonEmptyList a +reverse = wrappedOperation "reverse" L.reverse + +filter :: forall a. (a -> Boolean) -> NonEmptyList a -> L.List a +filter = lift <<< L.filter + +filterM :: forall m a. Monad m => (a -> m Boolean) -> NonEmptyList a -> m (L.List a) +filterM = lift <<< L.filterM + +mapMaybe :: forall a b. (a -> Maybe b) -> NonEmptyList a -> L.List b +mapMaybe = lift <<< L.mapMaybe + +catMaybes :: forall a. NonEmptyList (Maybe a) -> L.List a +catMaybes = lift L.catMaybes + +concat :: forall a. NonEmptyList (NonEmptyList a) -> NonEmptyList a +concat = (_ >>= id) + concatMap :: forall a b. (a -> NonEmptyList b) -> NonEmptyList a -> NonEmptyList b concatMap = flip bind @@ -86,9 +219,72 @@ appendFoldable :: forall t a. Foldable t => NonEmptyList a -> t a -> NonEmptyLis appendFoldable (NonEmptyList (x :| xs)) ys = NonEmptyList (x :| (xs <> L.fromFoldable ys)) +mapWithIndex :: forall a b. (Int -> a -> b) -> NonEmptyList a -> NonEmptyList b +mapWithIndex = wrappedOperation "mapWithIndex" <<< L.mapWithIndex + sort :: forall a. Ord a => NonEmptyList a -> NonEmptyList a sort xs = sortBy compare xs sortBy :: forall a. (a -> a -> Ordering) -> NonEmptyList a -> NonEmptyList a -sortBy cmp xs = unsafeFromList $ L.sortBy cmp (toList xs) - where unsafeFromList ys = unsafePartial $ fromJust $ fromList ys +sortBy = wrappedOperation "sortBy" <<< L.sortBy + +take :: forall a. Int -> NonEmptyList a -> L.List a +take = lift <<< L.take + +takeWhile :: forall a. (a -> Boolean) -> NonEmptyList a -> L.List a +takeWhile = lift <<< L.takeWhile + +drop :: forall a. Int -> NonEmptyList a -> L.List a +drop = lift <<< L.drop + +dropWhile :: forall a. (a -> Boolean) -> NonEmptyList a -> L.List a +dropWhile = lift <<< L.dropWhile + +span :: forall a. (a -> Boolean) -> NonEmptyList a -> { init :: L.List a, rest :: L.List a } +span = lift <<< L.span + +group :: forall a. Eq a => NonEmptyList a -> NonEmptyList (NonEmptyList a) +group = wrappedOperation "group" L.group + +group' :: forall a. Ord a => NonEmptyList a -> NonEmptyList (NonEmptyList a) +group' = wrappedOperation "group'" L.group' + +groupBy :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList (NonEmptyList a) +groupBy = wrappedOperation "groupBy" <<< L.groupBy + +partition :: forall a. (a -> Boolean) -> NonEmptyList a -> { yes :: L.List a, no :: L.List a } +partition = lift <<< L.partition + +nub :: forall a. Eq a => NonEmptyList a -> NonEmptyList a +nub = wrappedOperation "nub" L.nub + +nubBy :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList a +nubBy = wrappedOperation "nubBy" <<< L.nubBy + +union :: forall a. Eq a => NonEmptyList a -> NonEmptyList a -> NonEmptyList a +union = wrappedOperation2 "union" L.union + +unionBy :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList a -> NonEmptyList a +unionBy = wrappedOperation2 "unionBy" <<< L.unionBy + +intersect :: forall a. Eq a => NonEmptyList a -> NonEmptyList a -> NonEmptyList a +intersect = wrappedOperation2 "intersect" L.intersect + +intersectBy :: forall a. (a -> a -> Boolean) -> NonEmptyList a -> NonEmptyList a -> NonEmptyList a +intersectBy = wrappedOperation2 "intersectBy" <<< L.intersectBy + +zipWith :: forall a b c. (a -> b -> c) -> NonEmptyList a -> NonEmptyList b -> NonEmptyList c +zipWith f (NonEmptyList (x :| xs)) (NonEmptyList (y :| ys)) = + NonEmptyList (f x y :| L.zipWith f xs ys) + +zipWithA :: forall m a b c. Applicative m => (a -> b -> m c) -> NonEmptyList a -> NonEmptyList b -> m (NonEmptyList c) +zipWithA f xs ys = sequence1 (zipWith f xs ys) + +zip :: forall a b. NonEmptyList a -> NonEmptyList b -> NonEmptyList (Tuple a b) +zip = zipWith Tuple + +unzip :: forall a b. NonEmptyList (Tuple a b) -> Tuple (NonEmptyList a) (NonEmptyList b) +unzip ts = Tuple (map fst ts) (map snd ts) + +foldM :: forall m a b. Monad m => (a -> b -> m a) -> a -> NonEmptyList b -> m a +foldM f a (NonEmptyList (b :| bs)) = f a b >>= \a' -> L.foldM f a' bs diff --git a/src/Data/List/Types.purs b/src/Data/List/Types.purs index dc1ff5a..8168b21 100644 --- a/src/Data/List/Types.purs +++ b/src/Data/List/Types.purs @@ -12,13 +12,15 @@ import Control.MonadZero (class MonadZero) import Control.Plus (class Plus) import Data.Eq (class Eq1, eq1) -import Data.Foldable (class Foldable, foldr, foldl, intercalate) +import Data.Foldable (class Foldable, foldl, foldr, intercalate) import Data.Maybe (Maybe(..)) import Data.Monoid (class Monoid, mempty) import Data.Newtype (class Newtype) import Data.NonEmpty (NonEmpty, (:|)) import Data.NonEmpty as NE import Data.Ord (class Ord1, compare1) +import Data.Semigroup.Foldable (class Foldable1) +import Data.Semigroup.Traversable (class Traversable1, traverse1) import Data.Traversable (class Traversable, traverse) import Data.Tuple (Tuple(..)) import Data.Unfoldable (class Unfoldable) @@ -128,6 +130,9 @@ newtype NonEmptyList a = NonEmptyList (NonEmpty List a) toList :: NonEmptyList ~> List toList (NonEmptyList (x :| xs)) = x : xs +nelCons :: forall a. a -> NonEmptyList a -> NonEmptyList a +nelCons a (NonEmptyList (b :| bs)) = NonEmptyList (a :| b : bs) + derive instance newtypeNonEmptyList :: Newtype (NonEmptyList a) _ derive newtype instance eqNonEmptyList :: Eq a => Eq (NonEmptyList a) @@ -172,3 +177,15 @@ instance semigroupNonEmptyList :: Semigroup (NonEmptyList a) where derive newtype instance foldableNonEmptyList :: Foldable NonEmptyList derive newtype instance traversableNonEmptyList :: Traversable NonEmptyList + +instance foldable1NonEmptyList :: Foldable1 NonEmptyList where + fold1 (NonEmptyList (a :| as)) = + foldl append a as + foldMap1 f (NonEmptyList (a :| as)) = + foldl (\acc -> append acc <<< f) (f a) as + +instance traversable1NonEmptyList :: Traversable1 NonEmptyList where + traverse1 f (NonEmptyList (a :| as)) = + foldl (\acc -> lift2 (flip nelCons) acc <<< f) (pure <$> f a) as + <#> case _ of NonEmptyList (x :| xs) → foldl (flip nelCons) (pure x) xs + sequence1 = traverse1 id diff --git a/test/Test/Data/List/NonEmpty.purs b/test/Test/Data/List/NonEmpty.purs index 3b244d2..81ba440 100644 --- a/test/Test/Data/List/NonEmpty.purs +++ b/test/Test/Data/List/NonEmpty.purs @@ -1,19 +1,246 @@ module Test.Data.List.NonEmpty (testNonEmptyList) where import Prelude -import Data.List (fromFoldable) -import Data.List.NonEmpty (NonEmptyList(..), sort, sortBy) -import Data.NonEmpty ((:|)) + import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, log) +import Data.Foldable (class Foldable, foldM, foldMap, foldl, length) +import Data.List as L +import Data.List.NonEmpty as NEL +import Data.Maybe (Maybe(..)) +import Data.Monoid.Additive (Additive(..)) +import Data.NonEmpty ((:|)) +import Data.Tuple (Tuple(..)) import Test.Assert (ASSERT, assert) testNonEmptyList :: forall eff. Eff (assert :: ASSERT, console :: CONSOLE | eff) Unit testNonEmptyList = do - let nel x xs = NonEmptyList $ x :| fromFoldable xs + let + nel :: ∀ 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 = L.fromFoldable + + log "singleton should construct a non-empty list with a single value" + assert $ NEL.singleton 1 == nel 1 [] + assert $ NEL.singleton "foo" == nel "foo" [] + + log "length should return the number of items in a non-empty list" + assert $ length (nel 1 []) == 1 + assert $ length (nel 1 [2, 3, 4, 5]) == 5 + + log "length should be stack-safe" + void $ pure $ NEL.length $ nel 0 (L.range 1 100000) + + log "snoc should add an item to the end of a non-empty list" + assert $ nel 1 [2, 3] `NEL.snoc` 4 == nel 1 [2, 3, 4] + + log "head should return a first value of a non-empty list" + assert $ NEL.head (nel "foo" ["bar"]) == "foo" + + log "last should return a last value of a non-empty list" + assert $ NEL.last (nel "foo" ["bar"]) == "bar" + + log "tail should return a list containing all the items in a non-empty list apart from the first" + assert $ NEL.tail (nel "foo" ["bar", "baz"]) == (l ["bar", "baz"]) + + log "init should return a list containing all the items in a non-empty list apart from the last" + assert $ NEL.init (nel "foo" ["bar", "baz"]) == (l ["foo", "bar"]) + + log "uncons should split a non-empty list into a head and tail record when there is at least one item" + let u1 = NEL.uncons (nel 1 []) + assert $ u1.head == 1 + assert $ u1.tail == l [] + let u2 = NEL.uncons (nel 1 [2, 3]) + assert $ u2.head == 1 + assert $ u2.tail == l [2, 3] + + log "unsnoc should split a non-empty list into an init and last record when there is at least one item" + let v1 = NEL.unsnoc (nel 1 []) + assert $ v1.init == l [] + assert $ v1.last == 1 + let v2 = NEL.unsnoc (nel 1 [2, 3]) + assert $ v2.init == l [1, 2] + assert $ v2.last == 3 + + log "(!!) should return Just x when the index is within the bounds of the non-empty list" + assert $ nel 1 [2, 3] NEL.!! 0 == (Just 1) + assert $ nel 1 [2, 3] NEL.!! 1 == (Just 2) + assert $ nel 1 [2, 3] NEL.!! 2 == (Just 3) + + log "(!!) should return Nothing when the index is outside of the bounds of the non-empty list" + assert $ nel 1 [2, 3] NEL.!! 6 == Nothing + assert $ nel 1 [2, 3] NEL.!! (-1) == Nothing + + log "elemIndex should return the index of an item that a predicate returns true for in a non-empty list" + assert $ NEL.elemIndex 1 (nel 1 [2, 1]) == Just 0 + assert $ NEL.elemIndex 4 (nel 1 [2, 1]) == Nothing + + log "elemLastIndex should return the last index of an item in a non-empty list" + assert $ NEL.elemLastIndex 1 (nel 1 [2, 1]) == Just 2 + assert $ NEL.elemLastIndex 4 (nel 1 [2, 1]) == Nothing + + log "findIndex should return the index of an item that a predicate returns true for in a non-empty list" + assert $ NEL.findIndex (_ /= 1) (nel 1 [2, 1]) == Just 1 + assert $ NEL.findIndex (_ == 3) (nel 1 [2, 1]) == Nothing + + log "findLastIndex should return the last index of an item in a non-empty list" + assert $ NEL.findLastIndex (_ /= 1) (nel 2 [1, 2]) == Just 2 + assert $ NEL.findLastIndex (_ == 3) (nel 2 [1, 2]) == Nothing + + log "insertAt should add an item at the specified index" + assert $ (NEL.insertAt 0 1 (nel 2 [3])) == Just (nel 1 [2, 3]) + assert $ (NEL.insertAt 1 1 (nel 2 [3])) == Just (nel 2 [1, 3]) + assert $ (NEL.insertAt 2 1 (nel 2 [3])) == Just (nel 2 [3, 1]) + + log "insertAt should return Nothing if the index is out of range" + assert $ (NEL.insertAt 2 1 (nel 0 [])) == Nothing + + log "updateAt should replace an item at the specified index" + assert $ (NEL.updateAt 0 9 (nel 1 [2, 3])) == Just (nel 9 [2, 3]) + assert $ (NEL.updateAt 1 9 (nel 1 [2, 3])) == Just (nel 1 [9, 3]) + + log "updateAt should return Nothing if the index is out of range" + assert $ (NEL.updateAt 1 9 (nel 0 [])) == Nothing + + log "modifyAt should update an item at the specified index" + assert $ (NEL.modifyAt 0 (_ + 1) (nel 1 [2, 3])) == Just (nel 2 [2, 3]) + assert $ (NEL.modifyAt 1 (_ + 1) (nel 1 [2, 3])) == Just (nel 1 [3, 3]) + + log "modifyAt should return Nothing if the index is out of range" + assert $ (NEL.modifyAt 1 (_ + 1) (nel 0 [])) == Nothing + + log "reverse should reverse the order of items in an list" + assert $ (NEL.reverse (nel 1 [2, 3])) == nel 3 [2, 1] + + log "concat should join an list of lists" + assert $ (NEL.concat (nel (nel 1 [2]) [nel 3 [4]])) == nel 1 [2, 3, 4] + + log "concatMap should be equivalent to (concat <<< map)" + assert $ NEL.concatMap doubleAndOrig (nel 1 [2, 3]) == NEL.concat (map doubleAndOrig (nel 1 [2, 3])) + + log "filter should remove items that don't match a predicate" + assert $ NEL.filter odd (nel 0 (L.range 1 10)) == l [1, 3, 5, 7, 9] + + log "filterM should remove items that don't match a predicate while using a monadic behaviour" + assert $ NEL.filterM (Just <<< odd) (nel 0 (L.range 1 10)) == Just (l [1, 3, 5, 7, 9]) + assert $ NEL.filterM (const Nothing) (nel 0 (L.range 1 10)) == Nothing + + log "mapMaybe should transform every item in an list, throwing out Nothing values" + assert $ NEL.mapMaybe (\x -> if x /= 0 then Just x else Nothing) (nel 0 [1, 0, 0, 2, 3]) == l [1, 2, 3] + + log "catMaybe should take an list of Maybe values and throw out Nothings" + assert $ NEL.catMaybes (nel Nothing [Just 2, Nothing, Just 4]) == l [2, 4] + + log "mapWithIndex should take a list of values and apply a function which also takes the index into account" + assert $ NEL.mapWithIndex (\x ix -> x + ix) (nel 0 [1, 2, 4]) == nel 0 [2, 4, 7] log "sort should reorder a non-empty list into ascending order based on the result of compare" - assert $ sort (nel 1 [3, 2, 5, 6, 4]) == nel 1 [2, 3, 4, 5, 6] + assert $ NEL.sort (nel 1 [3, 2, 5, 6, 4]) == nel 1 [2, 3, 4, 5, 6] + log "sortBy should reorder a non-empty list into ascending order based on the result of a comparison function" - assert $ sortBy (flip compare) (nel 1 [3, 2, 5, 6, 4]) == nel 6 [5, 4, 3, 2, 1] + assert $ NEL.sortBy (flip compare) (nel 1 [3, 2, 5, 6, 4]) == nel 6 [5, 4, 3, 2, 1] + + log "take should keep the specified number of items from the front of an list, discarding the rest" + assert $ (NEL.take 1 (nel 1 [2, 3])) == l [1] + assert $ (NEL.take 2 (nel 1 [2, 3])) == l [1, 2] + + log "takeWhile should keep all values that match a predicate from the front of an list" + assert $ (NEL.takeWhile (_ /= 2) (nel 1 [2, 3])) == l [1] + assert $ (NEL.takeWhile (_ /= 3) (nel 1 [2, 3])) == l [1, 2] + + log "drop should remove the specified number of items from the front of an list" + assert $ (NEL.drop 1 (nel 1 [2, 3])) == l [2, 3] + assert $ (NEL.drop 2 (nel 1 [2, 3])) == l [3] + + log "dropWhile should remove all values that match a predicate from the front of an list" + assert $ (NEL.dropWhile (_ /= 1) (nel 1 [2, 3])) == l [1, 2, 3] + assert $ (NEL.dropWhile (_ /= 2) (nel 1 [2, 3])) == l [2, 3] + + log "span should split an list in two based on a predicate" + let spanResult = NEL.span (_ < 4) (nel 1 [2, 3, 4, 5, 6, 7]) + assert $ spanResult.init == l [1, 2, 3] + assert $ spanResult.rest == l [4, 5, 6, 7] + + log "group should group consecutive equal elements into lists" + assert $ NEL.group (nel 1 [2, 2, 3, 3, 3, 1]) == nel (nel 1 []) [nel 2 [2], nel 3 [3, 3], nel 1 []] + + log "group' should sort then group consecutive equal elements into lists" + assert $ NEL.group' (nel 1 [2, 2, 3, 3, 3, 1]) == nel (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 $ 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 "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]) + assert $ partitioned.yes == l [5, 3, 4] + assert $ partitioned.no == l [1, 2] + + log "nub should remove duplicate elements from the list, keeping the first occurence" + assert $ NEL.nub (nel 1 [2, 2, 3, 4, 1]) == nel 1 [2, 3, 4] + + log "nubBy should remove duplicate items from the list using a supplied predicate" + let nubPred = \x y -> if odd x then false else x == y + assert $ NEL.nubBy nubPred (nel 1 [2, 2, 3, 3, 4, 4, 1]) == nel 1 [2, 3, 3, 4, 1] + + log "union should produce the union of two lists" + assert $ NEL.union (nel 1 [2, 3]) (nel 2 [3, 4]) == nel 1 [2, 3, 4] + assert $ NEL.union (nel 1 [1, 2, 3]) (nel 2 [3, 4]) == nel 1 [1, 2, 3, 4] + + log "unionBy should produce the union of two lists using the specified equality relation" + assert $ NEL.unionBy (\_ y -> y < 5) (nel 1 [2, 3]) (nel 2 [3, 4, 5, 6]) == nel 1 [2, 3, 5, 6] + + log "intersect should return the intersection of two lists" + assert $ NEL.intersect (nel 1 [2, 3, 4, 3, 2, 1]) (nel 1 [1, 2, 3]) == nel 1 [2, 3, 3, 2, 1] + + log "intersectBy should return the intersection of two lists using the specified equivalence relation" + assert $ NEL.intersectBy (\x y -> (x * 2) == y) (nel 1 [2, 3]) (nel 2 [6]) == nel 1 [3] + + log "zipWith should use the specified function to zip two lists together" + assert $ NEL.zipWith (\x y -> nel (show x) [y]) (nel 1 [2, 3]) (nel "a" ["b", "c"]) == nel (nel "1" ["a"]) [nel "2" ["b"], nel "3" ["c"]] + + log "zipWithA should use the specified function to zip two lists together" + assert $ NEL.zipWithA (\x y -> Just $ Tuple x y) (nel 1 [2, 3]) (nel "a" ["b", "c"]) == Just (nel (Tuple 1 "a") [Tuple 2 "b", Tuple 3 "c"]) + + log "zip should use the specified function to zip two lists together" + assert $ NEL.zip (nel 1 [2, 3]) (nel "a" ["b", "c"]) == nel (Tuple 1 "a") [Tuple 2 "b", Tuple 3 "c"] + + log "unzip should deconstruct a list of tuples into a tuple of lists" + log $ show $ NEL.unzip (nel (Tuple 1 "a") [Tuple 2 "b", Tuple 3 "c"]) + assert $ NEL.unzip (nel (Tuple 1 "a") [Tuple 2 "b", Tuple 3 "c"]) == Tuple (nel 1 [2, 3]) (nel "a" ["b", "c"]) + + log "foldM should perform a fold using a monadic step function" + assert $ foldM (\x y -> Just (x + y)) 0 (nel 1 (L.range 2 10)) == Just 55 + + log "foldl should be stack-safe" + void $ pure $ foldl (+) 0 $ nel 0 (L.range 1 100000) + + log "foldMap should be stack-safe" + void $ pure $ foldMap Additive $ nel 0 (L.range 1 100000) + + log "foldMap should be left-to-right" + assert $ foldMap show (nel 1 (L.range 2 5)) == "12345" + + log "map should maintain order" + assert $ nel 0 (L.range 1 5) == map id (nel 0 (L.range 1 5)) + + log "traverse1 should be stack-safe" + let xs = nel 0 (L.range 1 100000) + assert $ NEL.traverse1 Just xs == Just xs + + log "traverse1 should preserve order" + let ts = nel 0 [1, 2, 3, 4, 5] + assert $ NEL.traverse1 Just ts == Just ts + + log "append should concatenate two lists" + assert $ (nel 1 [2]) <> (nel 3 [4]) == (nel 1 [2, 3, 4]) + + log "append should be stack-safe" + void $ pure $ xs <> xs + +odd :: Int -> Boolean +odd n = n `mod` 2 /= zero + +doubleAndOrig :: Int -> NEL.NonEmptyList Int +doubleAndOrig x = NEL.NonEmptyList (x * 2 :| x L.: L.Nil)