matchable-0.1.2: src/Data/Bimatchable.hs
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