nonempty-containers 0.1.0.0 → 0.1.1.0
raw patch · 10 files changed
+123/−24 lines, 10 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Data.IntMap.NonEmpty.Internal: instance Control.Comonad.Comonad Data.IntMap.NonEmpty.Internal.NEIntMap
+ Data.Map.NonEmpty.Internal: instance Control.Comonad.Comonad (Data.Map.NonEmpty.Internal.NEMap k)
Files
- CHANGELOG.md +12/−0
- README.md +13/−5
- nonempty-containers.cabal +2/−2
- src/Data/IntMap/NonEmpty/Internal.hs +25/−2
- src/Data/Map/NonEmpty/Internal.hs +20/−4
- src/Data/Sequence/NonEmpty/Internal.hs +3/−5
- src/Data/Set/NonEmpty/Internal.hs +2/−4
- test/Spec.hs +2/−2
- test/Tests/IntMap.hs +21/−0
- test/Tests/Map.hs +23/−0
CHANGELOG.md view
@@ -1,6 +1,18 @@ Changelog ========= +Version 0.1.1.0+---------------++*December 8, 2018*++<https://github.com/mstksg/nonempty-containres/releases/tag/v0.1.1.0>++* `Comonad` instances added for `Map k` and `IntMap`, based on [Faucelme's+ suggestion][comonad]++[comonad]: https://www.reddit.com/r/haskell/comments/a1qjcy/nonemptycontainers_nonempty_variants_of/eat5r4h/+ Version 0.1.0.0 ---------------
README.md view
@@ -1,10 +1,12 @@-# nonempty-containers+# [nonempty-containers][] +[nonempty-containers]: http://hackage.haskell.org/package/nonempty-containers+ Efficient and optimized non-empty (by construction) versions of types from *[containers][]*. Inspired by *[non-empty-containers][]* library, except attempting a more faithful port (with under-the-hood optimizations) of the full *containers* API. Also contains a convenient typeclass abstraction for-converting betwewen non-empty and possibly-empty variants, as well as pattern+converting between non-empty and possibly-empty variants, as well as pattern synonym-based conversion methods. [containers]: http://hackage.haskell.org/package/containers@@ -43,7 +45,7 @@ | That b | These a b - mapEither :: (a -> Either b c) -> NEMap k a -> These (NEMap k c) (NEMap k c)+ mapEither :: (a -> Either b c) -> NEMap k a -> These (NEMap k b) (NEMap k c) ``` This preserves the invariance of non-emptiness: either we have a non-empty@@ -83,13 +85,19 @@ * [non-empty-containers][]: Similar approach with similar data types, but API is limited to a few choice functions.+* [nonemptymap][]: Another similar approach, but is limited only to `Map`,+ and is also not a complete API port.+* [non-empty-sequence][]: Similar to *nonemptymap*, but for `Seq`. Also not+ a complete API port. * [non-empty][]: Similar approach with similar data types, but is meant to be more general and work for a variety of more data types. * [nonempty-alternative][]: Similar approach, but is instead a generalized data type for all `Alternative` instances. -[non-empty]: http://hackage.haskell.org/package/non-empty-[nonempty-alternative]: http://hackage.haskell.org/package/nonempty-alternative+[nonemptymap]: https://hackage.haskell.org/package/nonemptymap+[non-empty-sequence]: https://hackage.haskell.org/package/non-empty-sequence+[non-empty]: https://hackage.haskell.org/package/non-empty+[nonempty-alternative]: https://hackage.haskell.org/package/nonempty-alternative Currently not implemented:
nonempty-containers.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: bc9e6411940584bc108b18b524d5f1ef9a293724bd59219e1b637fc2d2566ea3+-- hash: b3390900b6f311b7cb393b0a328b14e8230f664849c6776e7e225bb4abdaa6e1 name: nonempty-containers-version: 0.1.0.0+version: 0.1.1.0 synopsis: Non-empty variants of containers data types, with full API description: Efficient and optimized non-empty versions of types from /containers/. Inspired by /non-empty-containers/ library, except attempting a more
src/Data/IntMap/NonEmpty/Internal.hs view
@@ -56,6 +56,7 @@ ) where import Control.Applicative+import Control.Comonad import Control.DeepSeq import Data.Coerce import Data.Data@@ -73,6 +74,7 @@ import Text.Read import qualified Data.Foldable as F import qualified Data.IntMap as M+import qualified Data.List as L import qualified Data.Semigroup.Foldable as F1 -- | A non-empty (by construction) map from integer keys to values @a@. At@@ -548,8 +550,7 @@ -- elements in order of ascending keys, while 'IntMap' traverses positive -- keys first, then negative keys. instance Foldable1 NEIntMap where- fold1 (NEIntMap _ v m) = maybe v (v <>)- . getOption+ fold1 (NEIntMap _ v m) = option v (v <>) . F.foldMap (Option . Just) . M.elems $ m@@ -568,6 +569,28 @@ instance Traversable1 NEIntMap where traverse1 f = traverseWithKey1 (const f) {-# INLINE traverse1 #-}++-- | 'extract' gets the value at the minimal key, and 'duplicate' produces+-- a map of maps comprised of all keys from the original map greater than+-- or equal to the current key.+--+-- @since 0.1.1.0+instance Comonad NEIntMap where+ extract = neimV0+ {-# INLINE extract #-}+ -- We'd like to use 'M.mapAccumWithKey', but it traverses things in the+ -- wrong order.+ duplicate n0@(NEIntMap k0 _ m0) = NEIntMap k0 n0+ . M.fromDistinctAscList+ . snd+ . L.mapAccumL go m0+ . M.toList+ $ m0+ where+ go m (k, v) = (m', (k, NEIntMap k v m'))+ where+ !m' = M.deleteMin m+ {-# INLINE duplicate #-} -- | /O(n)/. Test if the internal map structure is valid. valid :: NEIntMap a -> Bool
src/Data/Map/NonEmpty/Internal.hs view
@@ -51,6 +51,7 @@ ) where import Control.Applicative+import Control.Comonad import Control.DeepSeq import Data.Coerce import Data.Data@@ -263,8 +264,7 @@ => (k -> a -> m) -> NEMap k a -> m-foldMapWithKey f (NEMap k0 v m) = maybe (f k0 v) (f k0 v <>)- . getOption+foldMapWithKey f (NEMap k0 v m) = option (f k0 v) (f k0 v <>) . M.foldMapWithKey (\k -> Option . Just . f k) $ m {-# INLINE foldMapWithKey #-}@@ -534,8 +534,7 @@ -- | Traverses elements in order of ascending keys instance Foldable1 (NEMap k) where- fold1 (NEMap _ v m) = maybe v (v <>)- . getOption+ fold1 (NEMap _ v m) = option v (v <>) . F.foldMap (Option . Just) $ m {-# INLINE fold1 #-}@@ -554,6 +553,23 @@ where m1 = traverse (MaybeApply . Left) m0 {-# INLINABLE sequence1 #-}++-- | 'extract' gets the value at the minimal key, and 'duplicate' produces+-- a map of maps comprised of all keys from the original map greater than+-- or equal to the current key.+--+-- @since 0.1.1.0+instance Comonad (NEMap k) where+ extract = nemV0+ {-# INLINE extract #-}+ duplicate n0@(NEMap k0 _ m0) = NEMap k0 n0 . snd+ . M.mapAccumWithKey go m0+ $ m0+ where+ go m k v = (m', NEMap k v m')+ where+ !m' = M.deleteMin m+ {-# INLINE duplicate #-} -- | /O(n)/. Test if the internal map structure is valid. valid :: Ord k => NEMap k a -> Bool
src/Data/Sequence/NonEmpty/Internal.hs view
@@ -301,8 +301,7 @@ -- a folding function that also depends on the element's index, and applies -- it to every element in the sequence. foldMapWithIndex :: Semigroup m => (Int -> a -> m) -> NESeq a -> m-foldMapWithIndex f (x :<|| xs) = maybe (f 0 x) (f 0 x <>)- . getOption+foldMapWithIndex f (x :<|| xs) = option (f 0 x) (f 0 x <>) . Seq.foldMapWithIndex (\i -> Option . Just . f (i + 1)) $ xs {-# INLINE foldMapWithIndex #-}@@ -416,7 +415,7 @@ extend = extended {-# INLINE extend #-} --- | 'foldr1', 'foldl', 'maximum', and 'minimum' are all total, unlike for+-- | 'foldr1', 'foldl1', 'maximum', and 'minimum' are all total, unlike for -- 'Seq'. instance Foldable NESeq where #if MIN_VERSION_base(4,11,0)@@ -452,8 +451,7 @@ {-# INLINE length #-} instance Foldable1 NESeq where- fold1 (x :<|| xs) = maybe x (x <>)- . getOption+ fold1 (x :<|| xs) = option x (x <>) . F.foldMap (Option . Just) $ xs {-# INLINE fold1 #-}
src/Data/Set/NonEmpty/Internal.hs view
@@ -368,14 +368,12 @@ -- | Traverses elements in ascending order instance Foldable1 NESet where- fold1 (NESet x s) = maybe x (x <>)- . getOption+ fold1 (NESet x s) = option x (x <>) . F.foldMap (Option . Just) $ s {-# INLINE fold1 #-} -- TODO: benchmark against maxView-based method- foldMap1 f (NESet x s) = maybe (f x) (f x <>)- . getOption+ foldMap1 f (NESet x s) = option (f x) (f x <>) . F.foldMap (Option . Just . f) $ s {-# INLINE foldMap1 #-}
test/Spec.hs view
@@ -1,7 +1,7 @@ +-- import Test.Tasty.Hedgehog+-- import Test.Tasty.Ingredients.ConsoleReporter import Test.Tasty-import Test.Tasty.Hedgehog-import Test.Tasty.Ingredients.ConsoleReporter import Tests.IntMap import Tests.IntSet import Tests.Map
test/Tests/IntMap.hs view
@@ -5,6 +5,7 @@ module Tests.IntMap (intMapTests) where import Control.Applicative+import Control.Comonad import Data.Coerce import Data.Foldable import Data.Functor.Identity@@ -109,6 +110,26 @@ Nothing -> True Just ys@(y :| _) -> x < y && ascending ys +prop_extract_duplicate :: Property+prop_extract_duplicate = property $ do+ n <- forAll neIntMapGen+ tripping n duplicate+ (Identity . extract)++prop_fmap_extract_duplicate :: Property+prop_fmap_extract_duplicate = property $ do+ n <- forAll neIntMapGen+ tripping n duplicate+ (Identity . fmap extract)++prop_duplicate_duplicate :: Property+prop_duplicate_duplicate = property $ do+ n <- forAll neIntMapGen+ let dd1 = duplicate . duplicate $ n+ dd2 = fmap duplicate . duplicate $ n+ assert $ NEM.valid dd1+ assert $ NEM.valid dd2+ dd1 === dd2
test/Tests/Map.hs view
@@ -4,6 +4,7 @@ module Tests.Map (mapTests) where import Control.Applicative+import Control.Comonad import Data.Coerce import Data.Foldable import Data.Functor.Identity@@ -110,6 +111,28 @@ ascending (x :| xs) = case NE.nonEmpty xs of Nothing -> True Just ys@(y :| _) -> x < y && ascending ys++prop_extract_duplicate :: Property+prop_extract_duplicate = property $ do+ n <- forAll neMapGen+ tripping n duplicate+ (Identity . extract)++prop_fmap_extract_duplicate :: Property+prop_fmap_extract_duplicate = property $ do+ n <- forAll neMapGen+ tripping n duplicate+ (Identity . fmap extract)++prop_duplicate_duplicate :: Property+prop_duplicate_duplicate = property $ do+ n <- forAll neMapGen+ let dd1 = duplicate . duplicate $ n+ dd2 = fmap duplicate . duplicate $ n+ assert $ NEM.valid dd1+ assert $ NEM.valid dd2+ dd1 === dd2+