diff --git a/containers-tests/tests/set-properties.hs b/containers-tests/tests/set-properties.hs index e5ec351f3..75cf563fa 100644 --- a/containers-tests/tests/set-properties.hs +++ b/containers-tests/tests/set-properties.hs @@ -104,6 +104,7 @@ main = defaultMain $ testGroup "set-properties" , testProperty "prop_splitRoot" prop_splitRoot , testProperty "prop_partition" prop_partition , testProperty "prop_filter" prop_filter + , testProperty "prop_filterA" prop_filterA , testProperty "prop_mapMaybe" prop_mapMaybe , testProperty "takeWhileAntitone" prop_takeWhileAntitone , testProperty "dropWhileAntitone" prop_dropWhileAntitone @@ -646,8 +647,19 @@ prop_partition :: Set Int -> Int -> Bool prop_partition s i = case partition odd s of (s1,s2) -> all odd (toList s1) && all even (toList s2) && s == s1 `union` s2 -prop_filter :: Set Int -> Int -> Bool -prop_filter s i = partition odd s == (filter odd s, filter even s) +prop_filter :: Set Int -> Fun Int Bool -> Property +prop_filter s f = + valid s' .&&. toList s' === List.filter (applyFun f) (toList s) + where + s' = filter (applyFun f) s + +prop_filterA :: Set Int -> Fun Int Bool -> Property +prop_filterA s f = + valid s' .&&. + xs === toList s .&&. + toList s' === List.filter (applyFun f) (toList s) + where + (xs, s') = filterA (\x -> ([x], applyFun f x)) s prop_mapMaybe :: Fun Int (Maybe Int) -> Set Int -> Property prop_mapMaybe f s = diff --git a/containers/src/Data/IntMap/Internal.hs b/containers/src/Data/IntMap/Internal.hs index fbfaaa730..99166fd29 100644 --- a/containers/src/Data/IntMap/Internal.hs +++ b/containers/src/Data/IntMap/Internal.hs @@ -185,6 +185,7 @@ module Data.IntMap.Internal ( , traverseMaybeMissing , traverseMissing , filterAMissing + , whenMissing -- ** Deprecated general combining function , mergeWithKey @@ -1650,6 +1651,44 @@ instance Monad f => Monad (WhenMissing f x) where Just r -> missingKey (f r) k x {-# INLINE (>>=) #-} +-- | Create a @WhenMissing@ from two functions. +-- +-- @whenMissing@ must be called with two functions @f@ and @g@ such that +-- @g = 'traverseMaybeWithKey' f@. @g@ may be a more efficient way of applying +-- @f@ to all key-value pairs in an @IntMap@. +-- +-- __Warning__: It is the caller's responsibility to ensure the above property. +-- +-- === __Examples__ +-- +-- @ +-- preserveMissing :: Applicative f => WhenMissing f x x +-- preserveMissing = whenMissing f g +-- where +-- f _k x = pure (Just x) +-- g m = pure m +-- -- Note that this satisfies g = traverseMaybeWithKey f +-- @ +-- +-- @ +-- import Data.Functor.Const (Const(..)) +-- import Data.Monoid (All(..)) +-- +-- -- For a usage of this, see examples on mergeA +-- isEmpty :: WhenMissing (Const All) x y +-- isEmpty = whenMissing f g +-- where +-- f _k _x = Const (All False) +-- g m = Const (All (null m)) +-- -- Note that this satisfies g = traverseMaybeWithKey f +-- @ +-- +-- @since FIXME +whenMissing + :: (Key -> x -> f (Maybe y)) + -> (IntMap x -> f (IntMap y)) + -> WhenMissing f x y +whenMissing = flip WhenMissing -- | Map covariantly over a @'WhenMissing' f x@. -- @@ -2182,6 +2221,38 @@ merge g1 g2 f = \m1 m2 -> -- site. To prevent excessive inlining, you should generally only use -- 'mergeA' to define custom combining functions. -- +-- === __Examples__ +-- +-- @ +-- data Pair a = Pair !a !a deriving Functor +-- +-- instance Applicative Pair where +-- pure x = Pair x x +-- liftA2 f (Pair x1 y1) (Pair x2 y2) = Pair (f x1 x2) (f y1 y2) +-- +-- -- | Calculate the left-biased union and intersection of two maps. +-- unionIntersection :: IntMap a -> IntMap a -> (IntMap a, IntMap a) +-- unionIntersection m1 m2 = +-- case mergeA preserveAndDropMissing preserveAndDropMissing preserveLeftMatched m1 m2 of +-- Pair mu mi -> (mu, mi) +-- where +-- -- use Pair to build the union and intersection together +-- preserveAndDropMissing = 'whenMissing' (\\_k x -> Pair (Just x) Nothing) (\\m -> Pair m empty) +-- preserveLeftMatched = 'zipWithMaybeAMatched' (\\_k x1 _x2 -> Pair (Just x1) (Just x1)) +-- @ +-- +-- @ +-- import Data.Functor.Const (Const(..)) +-- import Data.Monoid (All(..)) +-- +-- -- | Whether the keys of the first map are a subset of the keys of the second map. +-- keysAreSubsetOf :: IntMap a -> IntMap b -> Bool +-- keysAreSubsetOf m1 m2 = +-- getAll (getConst (mergeA isEmpty 'dropMissing' 'dropMatched' m1 m2)) +-- where +-- isEmpty = 'whenMissing' (\\_k _x -> Const (All False)) (\\m -> Const (All (null m))) +-- @ +-- -- @since 0.5.9 mergeA :: (Applicative f) diff --git a/containers/src/Data/IntMap/Merge/Lazy.hs b/containers/src/Data/IntMap/Merge/Lazy.hs index d12b9c9d8..299dd092b 100644 --- a/containers/src/Data/IntMap/Merge/Lazy.hs +++ b/containers/src/Data/IntMap/Merge/Lazy.hs @@ -72,6 +72,7 @@ module Data.IntMap.Merge.Lazy ( , traverseMaybeMissing , traverseMissing , filterAMissing + , whenMissing -- *** Covariant maps for tactics , mapWhenMissing diff --git a/containers/src/Data/IntMap/Merge/Strict.hs b/containers/src/Data/IntMap/Merge/Strict.hs index f3ecb57fe..6c0f48613 100644 --- a/containers/src/Data/IntMap/Merge/Strict.hs +++ b/containers/src/Data/IntMap/Merge/Strict.hs @@ -73,6 +73,7 @@ module Data.IntMap.Merge.Strict ( , traverseMaybeMissing , traverseMissing , filterAMissing + , Internal.whenMissing -- ** Covariant maps for tactics , mapWhenMissing @@ -99,6 +100,7 @@ import Data.IntMap.Internal , runWhenMatched , runWhenMissing ) +import qualified Data.IntMap.Internal as Internal import Data.IntMap.Strict.Internal import Prelude hiding (filter, map, foldl, foldr) diff --git a/containers/src/Data/Map/Internal.hs b/containers/src/Data/Map/Internal.hs index ea10a0f35..b8f0d9b31 100644 --- a/containers/src/Data/Map/Internal.hs +++ b/containers/src/Data/Map/Internal.hs @@ -225,6 +225,7 @@ module Data.Map.Internal ( , traverseMaybeMissing , traverseMissing , filterAMissing + , whenMissing -- ** Deprecated general combining function @@ -2125,6 +2126,43 @@ instance Monad f => Monad (WhenMissing f k x) where Just r -> missingKey (f r) k x {-# INLINE (>>=) #-} +-- | Create a @WhenMissing@ from two functions. +-- +-- @whenMissing@ must be called with two functions @f@ and @g@ such that +-- @g = 'traverseMaybeWithKey' f@. @g@ may be a more efficient way of applying +-- @f@ to all key-value pairs in a @Map@. +-- +-- __Warning__: It is the caller's responsibility to ensure the above property. +-- +-- === __Examples__ +-- +-- @ +-- preserveMissing :: Applicative f => WhenMissing f k x x +-- preserveMissing = whenMissing f g +-- where +-- f _k x = pure (Just x) +-- g m = pure m +-- -- Note that this satisfies g = traverseMaybeWithKey f +-- @ +-- +-- @ +-- import Data.Functor.Const (Const(..)) +-- import Data.Monoid (All(..)) +-- +-- -- For a usage of this, see examples on mergeA +-- isEmpty :: WhenMissing (Const All) k x y +-- isEmpty = whenMissing f g +-- where +-- f _k _x = Const (All False) +-- g m = Const (All (null m)) +-- -- Note that this satisfies g = traverseMaybeWithKey f +-- @ +-- +-- @since FIXME +whenMissing + :: (k -> x -> f (Maybe y)) -> (Map k x -> f (Map k y)) -> WhenMissing f k x y +whenMissing = flip WhenMissing + -- | Map covariantly over a @'WhenMissing' f k x@. -- -- @since 0.5.9 @@ -2618,6 +2656,38 @@ merge g1 g2 f = \m1 m2 -> runIdentity $ -- site. To prevent excessive inlining, you should generally only use -- 'mergeA' to define custom combining functions. -- +-- === __Examples__ +-- +-- @ +-- data Pair a = Pair !a !a deriving Functor +-- +-- instance Applicative Pair where +-- pure x = Pair x x +-- liftA2 f (Pair x1 y1) (Pair x2 y2) = Pair (f x1 x2) (f y1 y2) +-- +-- -- | Calculate the left-biased union and intersection of two maps. +-- unionIntersection :: Ord k => Map k a -> Map k a -> (Map k a, Map k a) +-- unionIntersection m1 m2 = +-- case mergeA preserveAndDropMissing preserveAndDropMissing preserveLeftMatched m1 m2 of +-- Pair mu mi -> (mu, mi) +-- where +-- -- use Pair to build the union and intersection together +-- preserveAndDropMissing = 'whenMissing' (\\_k x -> Pair (Just x) Nothing) (\\m -> Pair m empty) +-- preserveLeftMatched = 'zipWithMaybeAMatched' (\\_k x1 _x2 -> Pair (Just x1) (Just x1)) +-- @ +-- +-- @ +-- import Data.Functor.Const (Const(..)) +-- import Data.Monoid (All(..)) +-- +-- -- | Whether the keys of the first map are a subset of the keys of the second map. +-- keysAreSubsetOf :: Ord k => Map k a -> Map k b -> Bool +-- keysAreSubsetOf m1 m2 = +-- getAll (getConst (mergeA isEmpty 'dropMissing' 'dropMatched' m1 m2)) +-- where +-- isEmpty = 'whenMissing' (\\_k _x -> Const (All False)) (\\m -> Const (All (null m))) +-- @ +-- -- @since 0.5.9 mergeA :: (Applicative f, Ord k) diff --git a/containers/src/Data/Map/Merge/Lazy.hs b/containers/src/Data/Map/Merge/Lazy.hs index b30d2a313..cb276f432 100644 --- a/containers/src/Data/Map/Merge/Lazy.hs +++ b/containers/src/Data/Map/Merge/Lazy.hs @@ -72,6 +72,7 @@ module Data.Map.Merge.Lazy ( , traverseMaybeMissing , traverseMissing , filterAMissing + , whenMissing -- *** Covariant maps for tactics , mapWhenMissing diff --git a/containers/src/Data/Map/Merge/Set/Internal.hs b/containers/src/Data/Map/Merge/Set/Internal.hs index 7a4c4f1b8..ac73142a0 100644 --- a/containers/src/Data/Map/Merge/Set/Internal.hs +++ b/containers/src/Data/Map/Merge/Set/Internal.hs @@ -236,6 +236,37 @@ merge miss1 miss2 match = \t1 t2 -> runIdentity (mergeA miss1 miss2 match t1 t2) -- site. To prevent excessive inlining, you should generally only use -- 'mergeA' to define custom combining functions. -- +-- === __Examples__ +-- +-- @ +-- data Pair a = Pair !a !a deriving Functor +-- +-- instance Applicative Pair where +-- pure x = Pair x x +-- liftA2 f (Pair x1 y1) (Pair x2 y2) = Pair (f x1 x2) (f y1 y2) +-- +-- -- | Partition the map according to whether the keys appear in the set. +-- partitionKeys :: Ord k => Map k a -> Set k -> (Map k a, Map k a) +-- partitionKeys m s = +-- case mergeA dropAndPreserveMissing dropMissingSet preserveAndDropMatched m s of +-- Pair m1 m2 -> (m1, m2) +-- where +-- dropAndPreserveMissing = whenMissing (\\_k x -> Pair Nothing (Just x)) (\\m -> Pair empty m) +-- preserveAndDropMatched = traverseMaybeMatched (\\_k x -> Pair (Just x) Nothing) +-- @ +-- +-- @ +-- import Data.Functor.Const (Const(..)) +-- import Data.Monoid (All(..)) +-- +-- -- | Whether the keys of the map are a subset of the keys of the set. +-- keysAreSubsetOf :: Ord k => Map k a -> Set k -> Bool +-- keysAreSubsetOf m s = +-- getAll (getConst (mergeA isEmpty dropMissing 'dropMatched' m1 m2)) +-- where +-- isEmpty = whenMissing (\\_k _x -> Const (All False)) (\\m -> Const (All (null m))) +-- @ +-- -- @since FIXME mergeA :: (Applicative f, Ord k) diff --git a/containers/src/Data/Map/Merge/Set/Lazy.hs b/containers/src/Data/Map/Merge/Set/Lazy.hs index abab435ab..58e0faa60 100644 --- a/containers/src/Data/Map/Merge/Set/Lazy.hs +++ b/containers/src/Data/Map/Merge/Set/Lazy.hs @@ -57,6 +57,7 @@ module Data.Map.Merge.Set.Lazy , M.filterAMissing , M.traverseMissing , M.traverseMaybeMissing + , M.whenMissing -- *** @WhenMissingSet@ tactics , generateAMissingSet diff --git a/containers/src/Data/Map/Merge/Set/Strict.hs b/containers/src/Data/Map/Merge/Set/Strict.hs index bc110e5b6..9beab6581 100644 --- a/containers/src/Data/Map/Merge/Set/Strict.hs +++ b/containers/src/Data/Map/Merge/Set/Strict.hs @@ -60,6 +60,7 @@ module Data.Map.Merge.Set.Strict , MS.filterAMissing , MS.traverseMissing , MS.traverseMaybeMissing + , M.whenMissing -- *** @WhenMissingSet@ tactics , generateAMissingSet @@ -71,6 +72,7 @@ module Data.Map.Merge.Set.Strict ) where import qualified Data.Map.Strict.Internal as MS +import qualified Data.Map.Internal as M import qualified Data.Map.Merge.Set.Internal as Internal import Data.Map.Merge.Set.Internal (WhenMatched(..), WhenMissingSet(..)) diff --git a/containers/src/Data/Map/Merge/Strict.hs b/containers/src/Data/Map/Merge/Strict.hs index bf4aae28d..361949eb2 100644 --- a/containers/src/Data/Map/Merge/Strict.hs +++ b/containers/src/Data/Map/Merge/Strict.hs @@ -78,6 +78,7 @@ module Data.Map.Merge.Strict ( , traverseMaybeMissing , traverseMissing , filterAMissing + , Internal.whenMissing -- ** Covariant maps for tactics , mapWhenMissing @@ -89,4 +90,5 @@ module Data.Map.Merge.Strict ( , runWhenMissing ) where +import qualified Data.Map.Internal as Internal import Data.Map.Strict.Internal diff --git a/containers/src/Data/Set.hs b/containers/src/Data/Set.hs index c3e805410..432790548 100644 --- a/containers/src/Data/Set.hs +++ b/containers/src/Data/Set.hs @@ -143,6 +143,7 @@ module Data.Set ( -- * Filter , S.filter + , filterA , takeWhileAntitone , dropWhileAntitone , spanAntitone diff --git a/containers/src/Data/Set/Internal.hs b/containers/src/Data/Set/Internal.hs index 7c7a0955c..f9f0c6079 100644 --- a/containers/src/Data/Set/Internal.hs +++ b/containers/src/Data/Set/Internal.hs @@ -154,6 +154,7 @@ module Data.Set.Internal ( -- * Filter , filter + , filterA , takeWhileAntitone , dropWhileAntitone , spanAntitone @@ -224,6 +225,7 @@ module Data.Set.Internal ( , preserveMissing , filterMissing , filterAMissing + , whenMissing , runWhenMissing , WhenMatched(..) , SimpleWhenMatched @@ -982,6 +984,9 @@ filter p t@(Bin _ x l r) !l' = filter p l !r' = filter p r +-- | \(O(n)\). Keep all elements that satisfy the Applicative predicate. +-- +-- @since FIXME filterA :: Applicative f => (a -> f Bool) -> Set a -> f (Set a) filterA p = go where @@ -2289,6 +2294,42 @@ filterMatched f = WhenMatched (pure . f) filterAMatched :: (a -> f Bool) -> WhenMatched f a filterAMatched = WhenMatched +-- | Create a @WhenMissing@ from two functions. +-- +-- @whenMissing@ must be called with two functions @f@ and @g@ such that +-- @g = 'filterA' f@. @g@ may be a more efficient way of applying @f@ to all +-- elements in a @Set@. +-- +-- __Warning__: It is the caller's responsibility to ensure the above property. +-- +-- === __Examples__ +-- +-- @ +-- preserveMissing :: Applicative f => WhenMissing f a +-- preserveMissing = whenMissing f g +-- where +-- f _x = pure True +-- g s = pure s +-- -- Note that this satisfies g = filterA f +-- @ +-- +-- @ +-- import Data.Functor.Const (Const(..)) +-- import Data.Monoid (All(..)) +-- +-- -- For a usage of this, see examples on mergeA +-- isEmpty :: WhenMissing (Const All) a +-- isEmpty = whenMissing f g +-- where +-- f _x = Const (All False) +-- g s = Const (All (null s)) +-- -- Note that this satisfies g = filterA f +-- @ +-- +-- @since FIXME +whenMissing :: (a -> f Bool) -> (Set a -> f (Set a)) -> WhenMissing f a +whenMissing = flip WhenMissing + -- | Drop all the elements that are missing from the other set. -- -- @ @@ -2446,6 +2487,37 @@ merge g1 g2 f = \s1 s2 -> runIdentity (mergeA g1 g2 f s1 s2) -- site. To prevent excessive inlining, you should generally only use -- 'mergeA' to define custom combining functions. -- +-- === __Examples__ +-- +-- @ +-- data Pair a = Pair !a !a deriving Functor +-- +-- instance Applicative Pair where +-- pure x = Pair x x +-- liftA2 f (Pair x1 y1) (Pair x2 y2) = Pair (f x1 x2) (f y1 y2) +-- +-- -- | Calculate the union and intersection of two sets. +-- unionIntersection :: Ord a => Set a -> Set a -> (Set a, Set a) +-- unionIntersection m1 m2 = +-- case mergeA preserveAndDropMissing preserveAndDropMissing 'preserveMatched' m1 m2 of +-- Pair mu mi -> (mu, mi) +-- where +-- -- use Pair to build the union and intersection together +-- preserveAndDropMissing = 'whenMissing' (\\_x -> Pair True False) (\\s -> Pair s empty) +-- @ +-- +-- @ +-- import Data.Functor.Const (Const(..)) +-- import Data.Monoid (All(..)) +-- +-- -- | Whether the first set is a subset of the second set. +-- isSubsetOf :: Ord a => Set a -> Set a -> Bool +-- isSubsetOf m1 m2 = +-- getAll (getConst (mergeA isEmpty 'dropMissing' 'dropMatched' m1 m2)) +-- where +-- isEmpty = 'whenMissing' (\\_x -> Const (All False)) (\\s -> Const (All (null s))) +-- @ +-- -- @since FIXME mergeA :: (Applicative f, Ord a) diff --git a/containers/src/Data/Set/Merge.hs b/containers/src/Data/Set/Merge.hs index 3b927c003..c42f07d95 100644 --- a/containers/src/Data/Set/Merge.hs +++ b/containers/src/Data/Set/Merge.hs @@ -38,6 +38,7 @@ module Data.Set.Merge -- | The tactics described for 'merge' work for 'mergeA' as well. -- Furthermore, the following are available. , filterAMissing + , whenMissing -- *** @WhenMatched@ tactics -- | The tactics described for 'merge' work for 'mergeA' as well.