module Data.Bimatchable( Bimatchable(..), bimapRecovered, eq2Default, liftEq2Default ) where import Control.Applicative import Data.Bifunctor import Data.Functor.Classes import Data.Tagged -- | Containers that allows exact structural matching of two containers. -- -- @Bimatchable@ is 'Bifunctor'-version of 'Matchable'. -- It can compare and zip containers with two parameters. class (Eq2 t, Bifunctor t) => Bimatchable t where {- | 'bizipMatch' is to 'Data.Matchable.zipMatch' what 'bimap' is to 'fmap'. Decides if two structures match exactly. If they match, return zipped version of them. ==== Law Forall @x :: t a b@, @y :: t a' b'@, @z :: t (a,a') (b,b')@, > bizipMatch x y = Just z holds if and only if both of > x = bimap fst fst z > y = bimap snd snd z holds. Otherwise, @bizipMatch x y = Nothing@. ==== Example >>> bizipMatch (Left 1) (Left 'a') Just (Left (1,'a')) >>> bizipMatch (Right 1) (Right False) Just (Right (1,False)) >>> bizipMatch (Left 1) (Right False) Nothing -} bizipMatch :: t a b -> t a' b' -> Maybe (t (a,a') (b,b')) bizipMatch = bizipMatchWith (curry Just) (curry Just) {-| 'bizipMatchWith' is to 'Data.Matchable.zipMatchWith' what 'bimap' is to 'fmap'. Match two structures. If they match, zip them with given functions @(a -> a' -> Maybe a'')@ and @(b -> b -> Maybe b'')@. Passed functions can make whole match failby returning @Nothing@. ==== Law For any > x :: t a b > y :: t a' b' > f :: a -> a' -> Maybe a'' > g :: b -> b' -> Maybe b'' 'bizipMatchWith' must satisfy the following. - If there is a pair @(z :: t (a,a') (b,b'), w :: t a'' b'')@ such that fulfills all of the following three conditions, then @bizipMatchWith f g x y = Just w@. 1. @x = bimap fst fst z@ 2. @y = bimap snd snd z@ 3. @bimap (uncurry f) (uncurry g) z = bimap Just Just w@ - If there are no such pair, @bizipMatchWith f g x y = Nothing@. -} bizipMatchWith :: (a -> a' -> Maybe a'') -> (b -> b' -> Maybe b'') -> t a b -> t a' b' -> Maybe (t a'' b'') {-# MINIMAL bizipMatchWith #-} instance Bimatchable Either where bizipMatchWith u _ (Left a) (Left a') = Left <$> u a a' bizipMatchWith _ v (Right b) (Right b') = Right <$> v b b' bizipMatchWith _ _ _ _ = Nothing instance Bimatchable (,) where bizipMatch (a, b) (a', b') = Just ((a, a'), (b, b')) bizipMatchWith u v (a, b) (a', b') = (,) <$> u a a' <*> v b b' instance Bimatchable Const where bizipMatch (Const a) (Const a') = Just (Const (a, a')) bizipMatchWith u _ (Const a) (Const a') = Const <$> u a a' instance Bimatchable Tagged where bizipMatch (Tagged b) (Tagged b') = Just (Tagged (b, b')) bizipMatchWith _ v (Tagged b) (Tagged b') = Tagged <$> v b b' bimapRecovered :: (Bimatchable t) => (a -> a') -> (b -> b') -> t a b -> t a' b' bimapRecovered f g tab = case bizipMatchWith (const (Just . f)) (const (Just . g)) tab tab of Nothing -> error "bimapRecovered: Unlawful instance of Bimatchable" Just r -> r eq2Default :: (Bimatchable t, Eq a, Eq b) => t a b -> t a b -> Bool eq2Default = liftEq2Default (==) (==) liftEq2Default :: (Bimatchable t) => (a -> a' -> Bool) -> (b -> b' -> Bool) -> t a b -> t a' b' -> Bool liftEq2Default pa pb tab tab' = case bizipMatchWith u v tab tab' of Nothing -> False Just _ -> True where u a a' = if pa a a' then Just () else Nothing v b b' = if pb b b' then Just () else Nothing