From a6e2199ff6fe3b1a53b03554c82d98c5c778128d Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 16:13:44 +0100 Subject: [PATCH 01/20] Add `reverse` for `NonEmptyList` --- src/Data/List/NonEmpty.purs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 099ba7b..c974c10 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -13,6 +13,7 @@ module Data.List.NonEmpty , init , uncons , unsnoc + , reverse , length , concatMap , appendFoldable @@ -31,7 +32,7 @@ import Data.NonEmpty ((:|)) import Data.NonEmpty as NE import Data.Tuple (Tuple(..)) import Data.Unfoldable (class Unfoldable, unfoldr) -import Partial.Unsafe (unsafePartial) +import Partial.Unsafe (unsafePartial, unsafeCrashWith) toUnfoldable :: forall f. Unfoldable f => NonEmptyList ~> f toUnfoldable = @@ -79,6 +80,12 @@ unsnoc (NonEmptyList (x :| xs)) = case L.unsnoc xs of length :: forall a. NonEmptyList a -> Int length (NonEmptyList (x :| xs)) = 1 + L.length xs +reverse :: forall a. NonEmptyList a -> NonEmptyList a +reverse (NonEmptyList (x :| xs)) = + case L.reverse (x : xs) of + x' : xs' -> NonEmptyList (x' :| xs') + L.Nil -> unsafeCrashWith "Impossible: empty list in NonEmptyList.reverse" + concatMap :: forall a b. (a -> NonEmptyList b) -> NonEmptyList a -> NonEmptyList b concatMap = flip bind From d3f7d7ab5159e4d4cf626abf7adf0aa13001148f Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 16:15:59 +0100 Subject: [PATCH 02/20] Add `filter` and `filterM` for `NonEmptyList` --- src/Data/List/NonEmpty.purs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index c974c10..ea3677e 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -16,6 +16,8 @@ module Data.List.NonEmpty , reverse , length , concatMap + , filter + , filterM , appendFoldable , sort , sortBy @@ -86,6 +88,12 @@ reverse (NonEmptyList (x :| xs)) = x' : xs' -> NonEmptyList (x' :| xs') L.Nil -> unsafeCrashWith "Impossible: empty list in NonEmptyList.reverse" +filter :: forall a. (a -> Boolean) -> NonEmptyList a -> L.List a +filter f (NonEmptyList (x :| xs)) = L.filter f (x : xs) + +filterM :: forall m a. Monad m => (a -> m Boolean) -> NonEmptyList a -> m (L.List a) +filterM f (NonEmptyList (x :| xs)) = L.filterM f (x : xs) + concatMap :: forall a b. (a -> NonEmptyList b) -> NonEmptyList a -> NonEmptyList b concatMap = flip bind From 69001bc8ae49a6371ae868d9c8033a0875bd4289 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 16:17:30 +0100 Subject: [PATCH 03/20] Add `mapMaybe` and `catMaybes` for `NonEmptyList` --- src/Data/List/NonEmpty.purs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index ea3677e..76ce8f6 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -18,6 +18,8 @@ module Data.List.NonEmpty , concatMap , filter , filterM + , mapMaybe + , catMaybes , appendFoldable , sort , sortBy @@ -94,6 +96,12 @@ filter f (NonEmptyList (x :| xs)) = L.filter f (x : xs) filterM :: forall m a. Monad m => (a -> m Boolean) -> NonEmptyList a -> m (L.List a) filterM f (NonEmptyList (x :| xs)) = L.filterM f (x : xs) +mapMaybe :: forall a b. (a -> Maybe b) -> NonEmptyList a -> L.List b +mapMaybe f (NonEmptyList (x :| xs)) = L.mapMaybe f (x : xs) + +catMaybes :: forall a. NonEmptyList (Maybe a) -> L.List a +catMaybes (NonEmptyList (x :| xs)) = L.catMaybes (x : xs) + concatMap :: forall a b. (a -> NonEmptyList b) -> NonEmptyList a -> NonEmptyList b concatMap = flip bind From 451e875f649d02ee6d69dd8706d93a2d89e8dfd1 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 16:19:24 +0100 Subject: [PATCH 04/20] Add `mapWithIndex` for `NonEmptyList` --- src/Data/List/NonEmpty.purs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 76ce8f6..24f5054 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -21,6 +21,7 @@ module Data.List.NonEmpty , mapMaybe , catMaybes , appendFoldable + , mapWithIndex , sort , sortBy ) where @@ -109,6 +110,12 @@ 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 f (NonEmptyList (x :| xs)) = + case L.mapWithIndex f (x : xs) of + x' : xs' -> NonEmptyList (x' :| xs') + L.Nil -> unsafeCrashWith "Impossible: empty list in NonEmptyList.mapWithIndex" + sort :: forall a. Ord a => NonEmptyList a -> NonEmptyList a sort xs = sortBy compare xs From 7b15f9330089ed9054f630a3f83c123f636edd81 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 16:20:10 +0100 Subject: [PATCH 05/20] Add re-exports to `NonEmptyList` --- src/Data/List/NonEmpty.purs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 24f5054..4320160 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -24,6 +24,7 @@ module Data.List.NonEmpty , mapWithIndex , sort , sortBy + , module Exports ) where import Prelude @@ -39,6 +40,9 @@ import Data.Tuple (Tuple(..)) import Data.Unfoldable (class Unfoldable, unfoldr) import Partial.Unsafe (unsafePartial, unsafeCrashWith) +import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports +import Data.Traversable (scanl, scanr) as Exports + toUnfoldable :: forall f. Unfoldable f => NonEmptyList ~> f toUnfoldable = unfoldr (\xs -> (\rec -> Tuple rec.head rec.tail) <$> L.uncons xs) <<< toList From 1b5c933e7dfdf21b852e77e6480de4b7304e2311 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 16:26:54 +0100 Subject: [PATCH 06/20] Implement NEL ops in terms of `wrappedOperation` --- src/Data/List/NonEmpty.purs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 4320160..233b776 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -33,16 +33,29 @@ 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.Unfoldable (class Unfoldable, unfoldr) -import Partial.Unsafe (unsafePartial, unsafeCrashWith) +import Partial.Unsafe (unsafeCrashWith) import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports import Data.Traversable (scanl, scanr) as Exports +-- | Internal function: any structure-preserving operation on a list also +-- | applies to a NEL, this function is a helper for defining those cases. +wrappedOperation + :: forall b a + . 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) + toUnfoldable :: forall f. Unfoldable f => NonEmptyList ~> f toUnfoldable = unfoldr (\xs -> (\rec -> Tuple rec.head rec.tail) <$> L.uncons xs) <<< toList @@ -90,10 +103,7 @@ length :: forall a. NonEmptyList a -> Int length (NonEmptyList (x :| xs)) = 1 + L.length xs reverse :: forall a. NonEmptyList a -> NonEmptyList a -reverse (NonEmptyList (x :| xs)) = - case L.reverse (x : xs) of - x' : xs' -> NonEmptyList (x' :| xs') - L.Nil -> unsafeCrashWith "Impossible: empty list in NonEmptyList.reverse" +reverse = wrappedOperation "reverse" L.reverse filter :: forall a. (a -> Boolean) -> NonEmptyList a -> L.List a filter f (NonEmptyList (x :| xs)) = L.filter f (x : xs) @@ -115,14 +125,10 @@ appendFoldable (NonEmptyList (x :| xs)) ys = NonEmptyList (x :| (xs <> L.fromFoldable ys)) mapWithIndex :: forall a b. (Int -> a -> b) -> NonEmptyList a -> NonEmptyList b -mapWithIndex f (NonEmptyList (x :| xs)) = - case L.mapWithIndex f (x : xs) of - x' : xs' -> NonEmptyList (x' :| xs') - L.Nil -> unsafeCrashWith "Impossible: empty list in NonEmptyList.mapWithIndex" +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 From 69fd915d399b399bffdbd2384c12d7870729d8dc Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 16:27:58 +0100 Subject: [PATCH 07/20] Add `foldM` for `NonEmptyList` --- src/Data/List/NonEmpty.purs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 233b776..16efbe0 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -24,6 +24,7 @@ module Data.List.NonEmpty , mapWithIndex , sort , sortBy + , foldM , module Exports ) where @@ -132,3 +133,6 @@ sort xs = sortBy compare xs sortBy :: forall a. (a -> a -> Ordering) -> NonEmptyList a -> NonEmptyList a sortBy = wrappedOperation "sortBy" <<< L.sortBy + +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 From 0d23c599bf7683b328ebebdb79f409c77b19f967 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 17:01:31 +0100 Subject: [PATCH 08/20] Add `Foldable1` and `Traversable1` instances for NEL --- src/Data/List/NonEmpty.purs | 5 ++++- src/Data/List/Types.purs | 19 ++++++++++++++++++- test/Test/Data/List/NonEmpty.purs | 20 +++++++++++++++----- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 16efbe0..7bcba52 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -42,8 +42,11 @@ import Data.Unfoldable (class Unfoldable, unfoldr) import Partial.Unsafe (unsafeCrashWith) import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports +import Data.Semigroup.Foldable (fold1, foldMap1, for1_, sequence1_, traverse1_) as Exports +import Data.Semigroup.Traversable (sequence1, traverse1, traverse1Default) as Exports import Data.Traversable (scanl, scanr) as Exports + -- | Internal function: any structure-preserving operation on a list also -- | applies to a NEL, this function is a helper for defining those cases. wrappedOperation @@ -55,7 +58,7 @@ wrappedOperation 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) + L.Nil -> unsafeCrashWith ("Impossible: empty list in NonEmptyList " <> name) toUnfoldable :: forall f. Unfoldable f => NonEmptyList ~> f toUnfoldable = 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..b961659 100644 --- a/test/Test/Data/List/NonEmpty.purs +++ b/test/Test/Data/List/NonEmpty.purs @@ -1,9 +1,10 @@ module Test.Data.List.NonEmpty (testNonEmptyList) where import Prelude -import Data.List (fromFoldable) -import Data.List.NonEmpty (NonEmptyList(..), sort, sortBy) +import Data.List (fromFoldable, range) +import Data.List.NonEmpty as NEL import Data.NonEmpty ((:|)) +import Data.Maybe import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, log) import Test.Assert (ASSERT, assert) @@ -11,9 +12,18 @@ 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 x xs = NEL.NonEmptyList $ x :| fromFoldable xs 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 "traverse1 should be stack-safe" + let xs = NEL.NonEmptyList $ 0 :| range 1 100000 + assert $ NEL.traverse1 Just xs == Just xs + + log "traverse1 should preserve order" + let xs = nel 0 [1, 2, 3, 4, 5] + assert $ NEL.traverse1 Just xs == Just xs From 1874dcbbe284ff44800edbfe7dfe20b06d0d598c Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 17:02:07 +0100 Subject: [PATCH 09/20] Add zip functions for NEL --- src/Data/List/NonEmpty.purs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 7bcba52..b858d45 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -30,13 +30,14 @@ module Data.List.NonEmpty import Prelude -import Data.Foldable (class Foldable) +import Data.Foldable (class Foldable, foldr) import Data.List ((:)) import Data.List as L import Data.List.Types (NonEmptyList(..)) import Data.Maybe (Maybe(..), fromMaybe, maybe) import Data.NonEmpty ((:|)) import Data.NonEmpty as NE +import Data.Semigroup.Traversable (sequence1) import Data.Tuple (Tuple(..)) import Data.Unfoldable (class Unfoldable, unfoldr) import Partial.Unsafe (unsafeCrashWith) @@ -137,5 +138,22 @@ sort xs = sortBy compare xs sortBy :: forall a. (a -> a -> Ordering) -> NonEmptyList a -> NonEmptyList a sortBy = wrappedOperation "sortBy" <<< L.sortBy +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 (NonEmptyList (Tuple x y :| xs)) = + foldr + (\(Tuple a b) (Tuple as bs) -> Tuple (cons a as) (cons b bs)) + (Tuple (pure x) (pure y)) + xs + 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 From 4cbb565a4010b030a773d3cb6d44bdcaacdf5831 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 17:07:59 +0100 Subject: [PATCH 10/20] Add nub and union functions --- src/Data/List/NonEmpty.purs | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index b858d45..cb5c841 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -24,6 +24,10 @@ module Data.List.NonEmpty , mapWithIndex , sort , sortBy + , nub + , nubBy + , union + , unionBy , foldM , module Exports ) where @@ -48,10 +52,11 @@ import Data.Semigroup.Traversable (sequence1, traverse1, traverse1Default) as Ex import Data.Traversable (scanl, scanr) as Exports --- | Internal function: any structure-preserving operation on a list also --- | applies to a NEL, this function is a helper for defining those cases. +-- | 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 b a + :: forall a b . String -> (L.List a -> L.List b) -> NonEmptyList a @@ -61,6 +66,19 @@ wrappedOperation name f (NonEmptyList (x :| xs)) = 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) + toUnfoldable :: forall f. Unfoldable f => NonEmptyList ~> f toUnfoldable = unfoldr (\xs -> (\rec -> Tuple rec.head rec.tail) <$> L.uncons xs) <<< toList @@ -138,6 +156,18 @@ sort xs = sortBy compare xs sortBy :: forall a. (a -> a -> Ordering) -> NonEmptyList a -> NonEmptyList a sortBy = wrappedOperation "sortBy" <<< L.sortBy +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 + 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) From 4d4c58406a0abd2b4d8e3f49f7c9d574a801a7e7 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 17:08:24 +0100 Subject: [PATCH 11/20] Export zip functions --- src/Data/List/NonEmpty.purs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index cb5c841..cb1ae6a 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -28,6 +28,10 @@ module Data.List.NonEmpty , nubBy , union , unionBy + , zipWith + , zipWithA + , zip + , unzip , foldM , module Exports ) where From 71a094fd6a4276148eb0e53b2822c9c567fa655d Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 17:13:25 +0100 Subject: [PATCH 12/20] Add take and drop functions for NEL --- src/Data/List/NonEmpty.purs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index cb1ae6a..2cf9a53 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -22,6 +22,10 @@ module Data.List.NonEmpty , catMaybes , appendFoldable , mapWithIndex + , take + , takeWhile + , drop + , dropWhile , sort , sortBy , nub @@ -83,6 +87,9 @@ wrappedOperation2 name f (NonEmptyList (x :| xs)) (NonEmptyList (y :| ys)) = x' : xs' -> NonEmptyList (x' :| xs') L.Nil -> unsafeCrashWith ("Impossible: empty list in NonEmptyList " <> name) +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 = unfoldr (\xs -> (\rec -> Tuple rec.head rec.tail) <$> L.uncons xs) <<< toList @@ -160,6 +167,18 @@ sort xs = sortBy compare xs sortBy :: forall a. (a -> a -> Ordering) -> NonEmptyList a -> NonEmptyList a 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 + nub :: forall a. Eq a => NonEmptyList a -> NonEmptyList a nub = wrappedOperation "nub" L.nub From 8193e5a64aac9d326fabab779ebd814f66d1e151 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 17:15:37 +0100 Subject: [PATCH 13/20] Implement filters in terms of `lift` --- src/Data/List/NonEmpty.purs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 2cf9a53..a9dbc01 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -87,6 +87,8 @@ wrappedOperation2 name f (NonEmptyList (x :| xs)) (NonEmptyList (y :| ys)) = 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) @@ -140,16 +142,16 @@ reverse :: forall a. NonEmptyList a -> NonEmptyList a reverse = wrappedOperation "reverse" L.reverse filter :: forall a. (a -> Boolean) -> NonEmptyList a -> L.List a -filter f (NonEmptyList (x :| xs)) = L.filter f (x : xs) +filter = lift <<< L.filter filterM :: forall m a. Monad m => (a -> m Boolean) -> NonEmptyList a -> m (L.List a) -filterM f (NonEmptyList (x :| xs)) = L.filterM f (x : xs) +filterM = lift <<< L.filterM mapMaybe :: forall a b. (a -> Maybe b) -> NonEmptyList a -> L.List b -mapMaybe f (NonEmptyList (x :| xs)) = L.mapMaybe f (x : xs) +mapMaybe = lift <<< L.mapMaybe catMaybes :: forall a. NonEmptyList (Maybe a) -> L.List a -catMaybes (NonEmptyList (x :| xs)) = L.catMaybes (x : xs) +catMaybes = lift L.catMaybes concatMap :: forall a b. (a -> NonEmptyList b) -> NonEmptyList a -> NonEmptyList b concatMap = flip bind From dfef7ecd9ee8bcc680fe3642bb2d60d995754fa2 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 17:20:26 +0100 Subject: [PATCH 14/20] Add concat for NEL --- src/Data/List/NonEmpty.purs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index a9dbc01..4a874d8 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 @@ -14,7 +15,7 @@ module Data.List.NonEmpty , uncons , unsnoc , reverse - , length + , concat , concatMap , filter , filterM @@ -22,12 +23,12 @@ module Data.List.NonEmpty , catMaybes , appendFoldable , mapWithIndex + , sort + , sortBy , take , takeWhile , drop , dropWhile - , sort - , sortBy , nub , nubBy , union @@ -153,6 +154,9 @@ 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 From e1a1faa092854ce03c28ba511ba52ba08608d87f Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 17:23:21 +0100 Subject: [PATCH 15/20] Add span and group functions for NEL --- src/Data/List/NonEmpty.purs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 4a874d8..e9d2725 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -185,6 +185,18 @@ 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 + nub :: forall a. Eq a => NonEmptyList a -> NonEmptyList a nub = wrappedOperation "nub" L.nub From ab5f98e427e857f85808bdbeffef9f2c5d12fa70 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 17:24:50 +0100 Subject: [PATCH 16/20] Add partition, group/span exports --- src/Data/List/NonEmpty.purs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index e9d2725..198e0c1 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -29,6 +29,11 @@ module Data.List.NonEmpty , takeWhile , drop , dropWhile + , span + , group + , group' + , groupBy + , partition , nub , nubBy , union @@ -197,6 +202,9 @@ 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 From 4450f3cce8ef0ac193d813bbfec6eb343539728b Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 17:27:16 +0100 Subject: [PATCH 17/20] Add intersections for NEL --- src/Data/List/NonEmpty.purs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index 198e0c1..ba0c8a9 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -38,6 +38,8 @@ module Data.List.NonEmpty , nubBy , union , unionBy + , intersect + , intersectBy , zipWith , zipWithA , zip @@ -217,6 +219,12 @@ 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) From 64e88e268ce7e30561be8f73fd45d3c3563ad039 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 17:39:22 +0100 Subject: [PATCH 18/20] Add various indexy functions for NEL --- src/Data/List/NonEmpty.purs | 49 +++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index ba0c8a9..b2630c8 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -14,6 +14,14 @@ module Data.List.NonEmpty , init , uncons , unsnoc + , (!!), index + , elemIndex + , elemLastIndex + , findIndex + , findLastIndex + , insertAt + , updateAt + , modifyAt , reverse , concat , concatMap @@ -146,6 +154,47 @@ 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 From 2831ef3f7b04952bc4fc50463ef798e2dd9c76d6 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 18:20:35 +0100 Subject: [PATCH 19/20] Add tests for NEL functions --- test/Test/Data/List/NonEmpty.purs | 233 +++++++++++++++++++++++++++++- 1 file changed, 225 insertions(+), 8 deletions(-) diff --git a/test/Test/Data/List/NonEmpty.purs b/test/Test/Data/List/NonEmpty.purs index b961659..81ba440 100644 --- a/test/Test/Data/List/NonEmpty.purs +++ b/test/Test/Data/List/NonEmpty.purs @@ -1,18 +1,140 @@ module Test.Data.List.NonEmpty (testNonEmptyList) where import Prelude -import Data.List (fromFoldable, range) -import Data.List.NonEmpty as NEL -import Data.NonEmpty ((:|)) -import Data.Maybe + 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 = NEL.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 $ NEL.sort (nel 1 [3, 2, 5, 6, 4]) == nel 1 [2, 3, 4, 5, 6] @@ -20,10 +142,105 @@ testNonEmptyList = do log "sortBy should reorder a non-empty list into ascending order based on the result of a comparison function" 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.NonEmptyList $ 0 :| range 1 100000 + let xs = nel 0 (L.range 1 100000) assert $ NEL.traverse1 Just xs == Just xs log "traverse1 should preserve order" - let xs = nel 0 [1, 2, 3, 4, 5] - assert $ NEL.traverse1 Just xs == Just xs + 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) From 6a28e0c33033610816ec40b06d46ddd7c53d6364 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 30 Jun 2017 18:25:07 +0100 Subject: [PATCH 20/20] Fix order of unzip for NEL --- src/Data/List/NonEmpty.purs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Data/List/NonEmpty.purs b/src/Data/List/NonEmpty.purs index b2630c8..5896755 100644 --- a/src/Data/List/NonEmpty.purs +++ b/src/Data/List/NonEmpty.purs @@ -58,7 +58,7 @@ module Data.List.NonEmpty import Prelude -import Data.Foldable (class Foldable, foldr) +import Data.Foldable (class Foldable) import Data.List ((:)) import Data.List as L import Data.List.Types (NonEmptyList(..)) @@ -66,7 +66,7 @@ import Data.Maybe (Maybe(..), fromMaybe, maybe) import Data.NonEmpty ((:|)) import Data.NonEmpty as NE import Data.Semigroup.Traversable (sequence1) -import Data.Tuple (Tuple(..)) +import Data.Tuple (Tuple(..), fst, snd) import Data.Unfoldable (class Unfoldable, unfoldr) import Partial.Unsafe (unsafeCrashWith) @@ -75,7 +75,6 @@ import Data.Semigroup.Foldable (fold1, foldMap1, for1_, sequence1_, traverse1_) 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. @@ -285,11 +284,7 @@ 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 (NonEmptyList (Tuple x y :| xs)) = - foldr - (\(Tuple a b) (Tuple as bs) -> Tuple (cons a as) (cons b bs)) - (Tuple (pure x) (pure y)) - xs +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