packages feed

merge 0.1.0.0 → 0.2.0.0

raw patch · 3 files changed

+114/−19 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Data.Merge: (*>) :: Applicative f => f a -> f b -> f b
+ Data.Merge: (<*) :: Applicative f => f a -> f b -> f a
+ Data.Merge: (<*>) :: Applicative f => f (a -> b) -> f a -> f b
+ Data.Merge: (<|>) :: Alternative f => f a -> f a -> f a
+ Data.Merge: Dual :: a -> Dual a
+ Data.Merge: First :: a -> First a
+ Data.Merge: Last :: a -> Last a
+ Data.Merge: Max :: a -> Max a
+ Data.Merge: Min :: a -> Min a
+ Data.Merge: Optional :: Maybe (Maybe a) -> Optional a
+ Data.Merge: Product :: a -> Product a
+ Data.Merge: Required :: Maybe a -> Required a
+ Data.Merge: Sum :: a -> Sum a
+ Data.Merge: [getDual] :: Dual a -> a
+ Data.Merge: [getFirst] :: First a -> a
+ Data.Merge: [getLast] :: Last a -> a
+ Data.Merge: [getMax] :: Max a -> a
+ Data.Merge: [getMin] :: Min a -> a
+ Data.Merge: [getProduct] :: Product a -> a
+ Data.Merge: [getSum] :: Sum a -> a
+ Data.Merge: [unOptional] :: Optional a -> Maybe (Maybe a)
+ Data.Merge: [unRequired] :: Required a -> Maybe a
+ Data.Merge: class Applicative f => Alternative (f :: Type -> Type)
+ Data.Merge: class Functor f => Applicative (f :: Type -> Type)
+ Data.Merge: class Profunctor (p :: Type -> Type -> Type)
+ Data.Merge: combine :: Semigroup a => (x -> a) -> Merge x a
+ Data.Merge: combineGen :: Semigroup s => (a -> s) -> (s -> Maybe a) -> (x -> a) -> Merge x a
+ Data.Merge: combineGenWith :: forall s a x. (s -> s -> s) -> (a -> s) -> (s -> Maybe a) -> (x -> a) -> Merge x a
+ Data.Merge: combineWith :: (a -> a -> a) -> (x -> a) -> Merge x a
+ Data.Merge: dimap :: Profunctor p => (a -> b) -> (c -> d) -> p b c -> p a d
+ Data.Merge: empty :: Alternative f => f a
+ Data.Merge: flattenMaybe :: Merge x (Maybe a) -> Merge x a
+ Data.Merge: infixl 3 <|>
+ Data.Merge: infixl 4 <*
+ Data.Merge: instance GHC.Classes.Eq a => GHC.Base.Monoid (Data.Merge.Optional a)
+ Data.Merge: instance GHC.Classes.Eq a => GHC.Base.Semigroup (Data.Merge.Optional a)
+ Data.Merge: instance GHC.Classes.Eq a => GHC.Base.Semigroup (Data.Merge.Required a)
+ Data.Merge: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Merge.Optional a)
+ Data.Merge: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Merge.Required a)
+ Data.Merge: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Merge.Optional a)
+ Data.Merge: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Merge.Required a)
+ Data.Merge: instance GHC.Generics.Generic (Data.Merge.Optional a)
+ Data.Merge: instance GHC.Generics.Generic (Data.Merge.Required a)
+ Data.Merge: instance GHC.Read.Read a => GHC.Read.Read (Data.Merge.Optional a)
+ Data.Merge: instance GHC.Read.Read a => GHC.Read.Read (Data.Merge.Required a)
+ Data.Merge: instance GHC.Show.Show a => GHC.Show.Show (Data.Merge.Optional a)
+ Data.Merge: instance GHC.Show.Show a => GHC.Show.Show (Data.Merge.Required a)
+ Data.Merge: lmap :: Profunctor p => (a -> b) -> p b c -> p a c
+ Data.Merge: many :: Alternative f => f a -> f [a]
+ Data.Merge: newtype Dual a
+ Data.Merge: newtype First a
+ Data.Merge: newtype Last a
+ Data.Merge: newtype Max a
+ Data.Merge: newtype Min a
+ Data.Merge: newtype Optional a
+ Data.Merge: newtype Product a
+ Data.Merge: newtype Required a
+ Data.Merge: newtype Sum a
+ Data.Merge: optionalToRequired :: Optional a -> Required a
+ Data.Merge: pure :: Applicative f => a -> f a
+ Data.Merge: requiredToOptional :: Required a -> Optional a
+ Data.Merge: rmap :: Profunctor p => (b -> c) -> p a b -> p a c
+ Data.Merge: some :: Alternative f => f a -> f [a]

Files

merge.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               merge-version:            0.1.0.0+version:            0.2.0.0 synopsis:           A functor for consistent merging of information description:        A functor for consistent merging of information. author:             Samuel Schlesinger
src/Data/Merge.hs view
@@ -1,3 +1,10 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE RankNTypes #-} {- | Name: Data.Merge Description: To describe merging of data types.@@ -5,11 +12,42 @@ Copyright: Samuel Schlesinger 2021 (c) -} {-# LANGUAGE BlockArguments #-}-module Data.Merge where+module Data.Merge+  ( Merge (Merge, runMerge)+  , merge+    -- * Construction+  , optional+  , required+  , combine+  , combineWith+  , combineGen+  , combineGenWith+  , Alternative(..)+  , Applicative(..)+    -- * Modification+  , flattenMaybe+  , Profunctor(..)+    -- * Useful Semigroups+  , Optional(..)+  , Required(..)+  , requiredToOptional+  , optionalToRequired+  , Last (..)+  , First (..)+  , Product (..)+  , Sum (..)+  , Dual (..)+  , Max (..)+  , Min (..)+  ) where +import GHC.Generics (Generic)+import Data.Typeable (Typeable) import Control.Monad (join)+import Data.Coerce (Coercible, coerce) import Control.Applicative (Alternative (..)) import Data.Profunctor (Profunctor (..))+import Data.Semigroup (Last (..), First (..), Product (..), Sum (..), Dual (..), Max (..), Min (..))  -- | Describes the merging of two values of the same type -- into some other type. Represented as a 'Maybe' valued@@ -21,6 +59,10 @@ -- > mergeExamples = Example <$> required a <*> optional b newtype Merge x a = Merge { runMerge :: x -> x -> Maybe a } +-- | Flattens a 'Maybe' layer inside of a 'Merge'+flattenMaybe :: Merge x (Maybe a) -> Merge x a+flattenMaybe (Merge f) = Merge \x x' -> join (f x x')+ -- | The most general combinator for constructing 'Merge's. merge :: (x -> x -> Maybe a) -> Merge x a merge = Merge@@ -50,17 +92,66 @@  -- | Meant to be used to merge optional fields in a record. optional :: Eq a => (x -> Maybe a) -> Merge x (Maybe a)-optional f = Merge (\x x' -> go (f x) (f x'))  where-  go (Just x) (Just x')-    | x == x' = Just (Just x)-    | otherwise = Nothing-  go Nothing (Just x) = Just (Just x)-  go (Just x) Nothing = Just (Just x)-  go Nothing Nothing = Just Nothing+optional = combineGen (maybe (Optional (Just Nothing)) (Optional . Just . Just)) unOptional  -- | Meant to be used to merge required fields in a record. required :: Eq a => (x -> a) -> Merge x a-required f = Merge (\x x' -> go (f x) (f x'))  where-  go x x'-    | x == x' = Just x-    | otherwise = Nothing+required = combineGen (Required . Just) unRequired++-- | Associatively combine original fields of the record.+combine :: Semigroup a => (x -> a) -> Merge x a+combine = combineWith (<>)++-- | Combine original fields of the record with the given function.+combineWith :: (a -> a -> a) -> (x -> a) -> Merge x a+combineWith c f = Merge (\x x' -> go (f x) (f x')) where+  go x x' = Just (x `c` x')++-- | Sometimes, one can describe a merge strategy via a binary operator. 'Optional'+-- and 'Required' describe 'optional' and 'required', respectively, in this way.+combineGenWith :: forall s a x. (s -> s -> s) -> (a -> s) -> (s -> Maybe a) -> (x -> a) -> Merge x a+combineGenWith c g l f = flattenMaybe $ fmap l $ combineWith c (g . f)++-- | 'combineGen' specialized to 'Semigroup' operations.+combineGen :: Semigroup s => (a -> s) -> (s -> Maybe a) -> (x -> a) -> Merge x a+combineGen = combineGenWith (<>)++-- | This type's 'Semigroup' instance encodes the simple,+-- discrete lattice generated by any given set, excluding the+-- bottom.+newtype Required a = Required { unRequired :: Maybe a }+  deriving (Eq, Show, Read, Ord, Generic, Typeable)++-- | We can convert any 'Required' to an 'Optional'+-- without losing any information.+requiredToOptional :: Required a -> Optional a+requiredToOptional (Required ma) = Optional (fmap Just ma)++instance Eq a => Semigroup (Required a) where+  Required (Just a) <> Required (Just a')+    | a == a' = Required (Just a)+    | otherwise = Required Nothing+  Required _ <> Required _ = Required Nothing++-- | This type's 'Semigroup' instance encodes the simple,+-- deiscrete lattice generated by any given set.+newtype Optional a = Optional { unOptional :: Maybe (Maybe a) }+  deriving (Eq, Show, Read, Ord, Generic, Typeable)++-- | We can convert any 'Optional' to a 'Required',+-- entering the 'Required's inconsistent state if+-- the value is absent from the optional.+optionalToRequired :: Optional a -> Required a+optionalToRequired = Required . join . unOptional++instance Eq a => Semigroup (Optional a) where+  Optional (Just (Just a)) <> Optional (Just (Just a'))+    | a == a' = Optional (Just (Just a))+    | otherwise = Optional Nothing+  Optional (Just Nothing) <> x = x+  x <> Optional (Just Nothing) = x+  Optional Nothing <> x = Optional Nothing+  x <> Optional Nothing = Optional Nothing++instance Eq a => Monoid (Optional a) where+  mempty = Optional (Just Nothing)
test/Test.hs view
@@ -12,11 +12,15 @@  main :: IO () main = do-  let merge = (,) <$> optional fst <*> required snd+  let merge = (,,) <$> optional (\(x,_,_) -> x) <*> required (\(_,x,_) -> x) <*> combine (\(_,_,x) -> x)+  let merge' = combine Max+  let merge'' = combine Last   requires "merge"-    [ runMerge merge (Just 10, 1) (Nothing, 1) == Just (Just 10, 1) -    , runMerge merge (Nothing, 1) (Nothing, 1) == Just (Nothing, 1)-    , runMerge merge (Nothing, 1) (Nothing, 2) == Nothing-    , runMerge merge (Just 10, 1) (Just 11, 1) == Nothing-    , runMerge merge (Just 10, 1) (Just 11, 2) == Nothing+    [ runMerge merge (Just 10, 1, []) (Nothing, 1, [1]) == Just (Just 10, 1, [1]) +    , runMerge merge (Nothing, 1, [2]) (Nothing, 1, [3]) == Just (Nothing, 1, [2, 3])+    , runMerge merge (Nothing, 1, [1, 2]) (Nothing, 2, [3, 4]) == Nothing+    , runMerge merge (Just 10, 1, [7]) (Just 11, 1, []) == Nothing+    , runMerge merge (Just 10, 1, []) (Just 11, 2, []) == Nothing+    , runMerge merge' 5 10 == Just (Max 10)+    , runMerge merge'' True False == Just (Last False)     ]