packages feed

merge 0.2.0.0 → 0.3.0.0

raw patch · 4 files changed

+140/−76 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.Merge: flattenMaybe :: Merge x (Maybe a) -> Merge x a
- Data.Merge: instance Data.Profunctor.Unsafe.Profunctor Data.Merge.Merge
- Data.Merge: instance GHC.Base.Alternative (Data.Merge.Merge x)
- Data.Merge: instance GHC.Base.Applicative (Data.Merge.Merge x)
- Data.Merge: instance GHC.Base.Functor (Data.Merge.Merge x)
- Data.Merge: instance GHC.Base.Monad (Data.Merge.Merge x)
- Data.Merge: instance GHC.Base.Semigroup a => GHC.Base.Monoid (Data.Merge.Merge x a)
- Data.Merge: instance GHC.Base.Semigroup a => GHC.Base.Semigroup (Data.Merge.Merge x a)
- 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: optionalToRequired :: Optional a -> Required a
- Data.Merge: requiredToOptional :: Required a -> Optional a
+ Data.Merge: (.?) :: Semigroup e => Merge e x a -> e -> Merge e x a
+ Data.Merge: Error :: e -> Validation e a
+ Data.Merge: Success :: a -> Validation e a
+ Data.Merge: data Validation e a
+ Data.Merge: flattenValidation :: Merge e x (Validation e a) -> Merge e x a
+ Data.Merge: infixl 6 .?
+ Data.Merge: instance (GHC.Base.Monoid e, GHC.Base.Semigroup a) => GHC.Base.Monoid (Data.Merge.Merge e x a)
+ Data.Merge: instance (GHC.Base.Monoid e, GHC.Classes.Eq a) => GHC.Base.Monoid (Data.Merge.Optional e a)
+ Data.Merge: instance (GHC.Base.Monoid e, GHC.Classes.Eq a) => GHC.Base.Semigroup (Data.Merge.Optional e a)
+ Data.Merge: instance (GHC.Base.Monoid e, GHC.Classes.Eq a) => GHC.Base.Semigroup (Data.Merge.Required e a)
+ Data.Merge: instance (GHC.Base.Semigroup e, GHC.Base.Semigroup a) => GHC.Base.Semigroup (Data.Merge.Merge e x a)
+ Data.Merge: instance (GHC.Classes.Eq e, GHC.Classes.Eq a) => GHC.Classes.Eq (Data.Merge.Optional e a)
+ Data.Merge: instance (GHC.Classes.Eq e, GHC.Classes.Eq a) => GHC.Classes.Eq (Data.Merge.Required e a)
+ Data.Merge: instance (GHC.Classes.Eq e, GHC.Classes.Eq a) => GHC.Classes.Eq (Data.Merge.Validation e a)
+ Data.Merge: instance (GHC.Classes.Ord e, GHC.Classes.Ord a) => GHC.Classes.Ord (Data.Merge.Optional e a)
+ Data.Merge: instance (GHC.Classes.Ord e, GHC.Classes.Ord a) => GHC.Classes.Ord (Data.Merge.Required e a)
+ Data.Merge: instance (GHC.Classes.Ord e, GHC.Classes.Ord a) => GHC.Classes.Ord (Data.Merge.Validation e a)
+ Data.Merge: instance (GHC.Read.Read e, GHC.Read.Read a) => GHC.Read.Read (Data.Merge.Optional e a)
+ Data.Merge: instance (GHC.Read.Read e, GHC.Read.Read a) => GHC.Read.Read (Data.Merge.Required e a)
+ Data.Merge: instance (GHC.Read.Read e, GHC.Read.Read a) => GHC.Read.Read (Data.Merge.Validation e a)
+ Data.Merge: instance (GHC.Show.Show e, GHC.Show.Show a) => GHC.Show.Show (Data.Merge.Optional e a)
+ Data.Merge: instance (GHC.Show.Show e, GHC.Show.Show a) => GHC.Show.Show (Data.Merge.Required e a)
+ Data.Merge: instance (GHC.Show.Show e, GHC.Show.Show a) => GHC.Show.Show (Data.Merge.Validation e a)
+ Data.Merge: instance Data.Bifunctor.Bifunctor Data.Merge.Validation
+ Data.Merge: instance Data.Profunctor.Unsafe.Profunctor (Data.Merge.Merge e)
+ Data.Merge: instance GHC.Base.Functor (Data.Merge.Merge e x)
+ Data.Merge: instance GHC.Base.Functor (Data.Merge.Validation e)
+ Data.Merge: instance GHC.Base.Monoid e => GHC.Base.Alternative (Data.Merge.Merge e x)
+ Data.Merge: instance GHC.Base.Monoid e => GHC.Base.Alternative (Data.Merge.Validation e)
+ Data.Merge: instance GHC.Base.Semigroup e => GHC.Base.Applicative (Data.Merge.Merge e x)
+ Data.Merge: instance GHC.Base.Semigroup e => GHC.Base.Applicative (Data.Merge.Validation e)
+ Data.Merge: instance GHC.Generics.Generic (Data.Merge.Optional e a)
+ Data.Merge: instance GHC.Generics.Generic (Data.Merge.Required e a)
+ Data.Merge: instance GHC.Generics.Generic (Data.Merge.Validation e a)
+ Data.Merge: validation :: (e -> r) -> (a -> r) -> Validation e a -> r
- Data.Merge: Merge :: (x -> x -> Maybe a) -> Merge x a
+ Data.Merge: Merge :: (x -> x -> Validation e a) -> Merge e x a
- Data.Merge: Optional :: Maybe (Maybe a) -> Optional a
+ Data.Merge: Optional :: Validation e (Maybe a) -> Optional e a
- Data.Merge: Required :: Maybe a -> Required a
+ Data.Merge: Required :: Validation e a -> Required e a
- Data.Merge: [runMerge] :: Merge x a -> x -> x -> Maybe a
+ Data.Merge: [runMerge] :: Merge e x a -> x -> x -> Validation e a
- Data.Merge: [unOptional] :: Optional a -> Maybe (Maybe a)
+ Data.Merge: [unOptional] :: Optional e a -> Validation e (Maybe a)
- Data.Merge: [unRequired] :: Required a -> Maybe a
+ Data.Merge: [unRequired] :: Required e a -> Validation e a
- Data.Merge: combine :: Semigroup a => (x -> a) -> Merge x a
+ Data.Merge: combine :: forall e a x. Semigroup a => (x -> a) -> Merge e x a
- Data.Merge: combineGen :: Semigroup s => (a -> s) -> (s -> Maybe a) -> (x -> a) -> Merge x a
+ Data.Merge: combineGen :: forall e a x s. Semigroup s => (a -> s) -> (s -> Validation e a) -> (x -> a) -> Merge e x a
- Data.Merge: combineGenWith :: forall s a x. (s -> s -> s) -> (a -> s) -> (s -> Maybe a) -> (x -> a) -> Merge x a
+ Data.Merge: combineGenWith :: forall e a x s. (s -> s -> s) -> (a -> s) -> (s -> Validation e a) -> (x -> a) -> Merge e x a
- Data.Merge: combineWith :: (a -> a -> a) -> (x -> a) -> Merge x a
+ Data.Merge: combineWith :: forall e a x. (a -> a -> a) -> (x -> a) -> Merge e x a
- Data.Merge: merge :: (x -> x -> Maybe a) -> Merge x a
+ Data.Merge: merge :: (x -> x -> Validation e a) -> Merge e x a
- Data.Merge: newtype Merge x a
+ Data.Merge: newtype Merge e x a
- Data.Merge: newtype Optional a
+ Data.Merge: newtype Optional e a
- Data.Merge: newtype Required a
+ Data.Merge: newtype Required e a
- Data.Merge: optional :: Eq a => (x -> Maybe a) -> Merge x (Maybe a)
+ Data.Merge: optional :: (Monoid e, Eq a) => (x -> Maybe a) -> Merge e x (Maybe a)
- Data.Merge: required :: Eq a => (x -> a) -> Merge x a
+ Data.Merge: required :: forall e a x. (Monoid e, Eq a) => (x -> a) -> Merge e x a

Files

README.md view
@@ -1,17 +1,39 @@ # Merge +Often, one finds themselves having multiple sources of knowledge+for some piece of data, and having to merge these together. Perhaps+we have a type representing partial information about a digital friend.+ ```haskell-data User = User+data Friend = Friend   { name :: Maybe Text+  , email :: Maybe Text+  , age :: Int   , pubKey :: PublicKey   }+``` -mergeUsers :: Merge User User-mergeUsers =+If we learn some information about a friend from someone, and some+from someone else, we'll want to merge that information to have a+more complete picture. That said, it might not succeed, as we may+have inconsistent information like two different names or different+public keys. We'll want a function of type:++```haskell+f :: Friend -> Friend -> Maybe Friend+```++That's the pattern that this library encapsulates!++```+mergeFriends :: Merge Friend Friend+mergeFriends =   User     <$> optional name+    <*> optional email+    <*> combine Max     <*> required pubKey -f :: User -> User -> Maybe User-f x y = runMerge mergeUsers x y+f :: Friend -> Friend -> Maybe Friend+f x y = runMerge mergeFriends x y ```
merge.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               merge-version:            0.2.0.0+version:            0.3.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,4 @@+{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE FlexibleContexts #-}@@ -13,9 +14,15 @@ -} {-# LANGUAGE BlockArguments #-} module Data.Merge-  ( Merge (Merge, runMerge)-  , merge+  ( +    -- * A Validation Applicative+    Validation(..)+  , validation+    -- * The Merge type+  , Merge (Merge, runMerge)     -- * Construction+  , (.?)+  , merge   , optional   , required   , combine@@ -25,13 +32,11 @@   , Alternative(..)   , Applicative(..)     -- * Modification-  , flattenMaybe+  , flattenValidation   , Profunctor(..)     -- * Useful Semigroups   , Optional(..)   , Required(..)-  , requiredToOptional-  , optionalToRequired   , Last (..)   , First (..)   , Product (..)@@ -47,8 +52,37 @@ import Data.Coerce (Coercible, coerce) import Control.Applicative (Alternative (..)) import Data.Profunctor (Profunctor (..))+import Data.Bifunctor (Bifunctor(..)) import Data.Semigroup (Last (..), First (..), Product (..), Sum (..), Dual (..), Max (..), Min (..)) +-- | Like 'Either', but with an 'Applicative' instance which+-- accumulates errors using their 'Semigroup' operation.+data Validation e a =+    Error e+  | Success a+  deriving (Functor, Eq, Ord, Show, Read, Generic, Typeable)++validation :: (e -> r) -> (a -> r) -> Validation e a -> r+validation f g = \case+  Error e -> f e+  Success a -> g a++instance Bifunctor Validation where+  bimap f g (Error e) = Error (f e)+  bimap f g (Success a) = Success (g a)++instance Semigroup e => Applicative (Validation e) where+  pure = Success+  Success f <*> Success x = Success (f x)+  Error e   <*> Error e'  = Error (e <> e')+  Error e   <*> _         = Error e+  _         <*> Error e'  = Error e'++instance Monoid e => Alternative (Validation e) where+  empty = Error mempty+  Success a <|> x = Success a+  Error e <|> x = x+ -- | Describes the merging of two values of the same type -- into some other type. Represented as a 'Maybe' valued -- function, one can also think of this as a predicate@@ -57,101 +91,101 @@ -- > data Example = Whatever { a :: Int, b :: Maybe Bool } -- > mergeExamples :: Merge Example Example -- > mergeExamples = Example <$> required a <*> optional b-newtype Merge x a = Merge { runMerge :: x -> x -> Maybe a }+newtype Merge e x a = Merge { runMerge :: x -> x -> Validation e a } +-- | Appends some errors. Useful for the combinators provided by this library,+-- which use 'mempty' to provide the default error type.+(.?) :: Semigroup e => Merge e x a -> e -> Merge e x a+m .? e = Merge \x x' -> bimap (e <>) id (runMerge m x x')++infixl 6 .?+ -- | 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')+flattenValidation :: Merge e x (Validation e a) -> Merge e x a+flattenValidation (Merge f) = Merge \x x' ->+  case f x x' of+    Error e -> Error e+    Success (Error e) -> Error e+    Success (Success a) -> Success a  -- | The most general combinator for constructing 'Merge's.-merge :: (x -> x -> Maybe a) -> Merge x a+merge :: (x -> x -> Validation e a) -> Merge e x a merge = Merge -instance Profunctor Merge where+instance Profunctor (Merge e) where   dimap l r (Merge f) = Merge \x x' -> r <$> f (l x) (l x') -instance Functor (Merge x) where+instance Functor (Merge e x) where   fmap = rmap -instance Applicative (Merge x) where-  pure x = Merge (\_ _ -> Just x)+instance Semigroup e => Applicative (Merge e x) where+  pure x = Merge (\_ _ -> Success x)   fa <*> a = Merge \x x' -> runMerge fa x x' <*> runMerge a x x' -instance Alternative (Merge x) where-  empty = Merge \_ _ -> Nothing+instance Monoid e => Alternative (Merge e x) where+  empty = Merge \_ _ -> empty   Merge f <|> Merge g = Merge \x x' -> f x x' <|> g x x' -instance Monad (Merge x) where-  a >>= f = Merge \x x' -> join $ fmap (\b -> runMerge b x x') $ fmap f $ runMerge a x x'--instance Semigroup a => Semigroup (Merge x a) where-  a <> b = Merge \x x' -> runMerge a x x' <> runMerge b x x'+instance (Semigroup e, Semigroup a) => Semigroup (Merge e x a) where+  a <> b = Merge \x x' -> (<>) <$> runMerge a x x' <*> runMerge b x x' -instance Semigroup a => Monoid (Merge x a) where-  mempty = Merge \_ _ -> mempty  +instance (Monoid e, Semigroup a) => Monoid (Merge e x a) where+  mempty = Merge \_ _ -> Error mempty    -- | Meant to be used to merge optional fields in a record.-optional :: Eq a => (x -> Maybe a) -> Merge x (Maybe a)-optional = combineGen (maybe (Optional (Just Nothing)) (Optional . Just . Just)) unOptional+optional :: (Monoid e, Eq a) => (x -> Maybe a) -> Merge e x (Maybe a)+optional = combineGen (maybe (Optional (Success Nothing)) (Optional . Success . Just)) unOptional  -- | Meant to be used to merge required fields in a record.-required :: Eq a => (x -> a) -> Merge x a-required = combineGen (Required . Just) unRequired+required :: forall e a x. (Monoid e, Eq a) => (x -> a) -> Merge e x a+required = combineGen (Required . Success) unRequired  -- | Associatively combine original fields of the record.-combine :: Semigroup a => (x -> a) -> Merge x a+combine :: forall e a x. Semigroup a => (x -> a) -> Merge e x a combine = combineWith (<>)  -- | Combine original fields of the record with the given function.-combineWith :: (a -> a -> a) -> (x -> a) -> Merge x a+combineWith :: forall e a x. (a -> a -> a) -> (x -> a) -> Merge e x a combineWith c f = Merge (\x x' -> go (f x) (f x')) where-  go x x' = Just (x `c` x')+  go x x' = Success (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)+combineGenWith :: forall e a x s. (s -> s -> s) -> (a -> s) -> (s -> Validation e a) -> (x -> a) -> Merge e x a+combineGenWith c g l f = flattenValidation $ 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 :: forall e a x s. Semigroup s => (a -> s) -> (s -> Validation e a) -> (x -> a) -> Merge e 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 }+newtype Required e a = Required { unRequired :: Validation e 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+instance (Monoid e, Eq a) => Semigroup (Required e a) where+  Required (Success a) <> Required (Success a')+    | a == a' = Required (Success a)+    | otherwise = Required (Error mempty)+  Required (Error e) <> Required (Success _) = Required (Error e)+  Required (Success _) <> Required (Error e') = Required (Error e')+  Required (Error e) <> Required (Error e') = Required (Error (e <> e'))  -- | This type's 'Semigroup' instance encodes the simple, -- deiscrete lattice generated by any given set.-newtype Optional a = Optional { unOptional :: Maybe (Maybe a) }+newtype Optional e a = Optional { unOptional :: Validation e (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 (Monoid e, Eq a) => Semigroup (Optional e a) where+  Optional (Success (Just a)) <> Optional (Success (Just a'))+    | a == a' = Optional (Success (Just a))+    | otherwise = Optional (Error mempty)+  Optional (Success Nothing) <> x = x+  x <> Optional (Success Nothing) = x+  Optional (Error e) <> Optional (Error e') = Optional (Error (e <> e'))+  x <> Optional (Error e) = Optional (Error e)+  Optional (Error e) <> x = Optional (Error e) -instance Eq a => Monoid (Optional a) where-  mempty = Optional (Just Nothing)+instance (Monoid e, Eq a) => Monoid (Optional e a) where+  mempty = Optional (Success Nothing)
test/Test.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ScopedTypeVariables #-} module Main where  import Control.Monad (mapM_)@@ -12,15 +14,21 @@  main :: IO () main = do-  let merge = (,,) <$> optional (\(x,_,_) -> x) <*> required (\(_,x,_) -> x) <*> combine (\(_,_,x) -> x)-  let merge' = combine Max-  let merge'' = combine Last+  let+    merge = (,,)+      <$> optional (\(x,_,_) -> x) .? ["fst"]+      <*> required (\(_,x,_) -> x) .? ["snd"]+      <*> combine (\(_,_,x) -> x)  .? ["thd"]+    merge' = combine Max .? ["max"]+    merge'' = combine Last .? ["last"]   requires "merge"-    [ 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)+    [ runMerge merge (Just 10, 1, []) (Nothing, 1, [1]) == Success (Just 10, 1, [1]) +    , runMerge merge (Nothing, 1, [2]) (Nothing, 1, [3]) == Success (Nothing, 1, [2, 3])+    , runMerge merge (Nothing, 1, [1, 2]) (Nothing, 2, [3, 4]) == Error ["snd"]+    , runMerge merge (Just 10, 1, [7]) (Just 11, 1, []) == Error ["fst"]+    , runMerge merge (Just 10, 1, []) (Just 11, 2, []) == Error ["fst", "snd"]+    , runMerge merge' 5 10 == Success (Max 10)+    , runMerge merge'' True False == Success (Last False)+    , (((,) <$> Error "Hello" <*> Success 10) :: Validation String (Bool, Int)) == Error "Hello"+    , (((,) <$> Success True <*> Success (10 :: Int)) :: Validation String (Bool, Int)) == Success (True, 10)     ]