packages feed

merge-0.1.0.0: src/Data/Merge.hs

{- |
Name: Data.Merge
Description: To describe merging of data types.
License: MIT
Copyright: Samuel Schlesinger 2021 (c)
-}
{-# LANGUAGE BlockArguments #-}
module Data.Merge where

import Control.Monad (join)
import Control.Applicative (Alternative (..))
import Data.Profunctor (Profunctor (..))

-- | 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
-- showing which pairs of values can be merged in this way.
--
-- > 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 }

-- | The most general combinator for constructing 'Merge's.
merge :: (x -> x -> Maybe a) -> Merge x a
merge = Merge

instance Profunctor Merge where
  dimap l r (Merge f) = Merge \x x' -> r <$> f (l x) (l x')

instance Functor (Merge x) where
  fmap = rmap

instance Applicative (Merge x) where
  pure x = Merge (\_ _ -> Just x)
  fa <*> a = Merge \x x' -> runMerge fa x x' <*> runMerge a x x'

instance Alternative (Merge x) where
  empty = Merge \_ _ -> Nothing
  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 a => Monoid (Merge x a) where
  mempty = Merge \_ _ -> mempty  

-- | 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

-- | 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