{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DeriveFunctor #-}
module Data.Matchable(
Matchable(..),
zipzipMatch,
fmapRecovered,
eqDefault,
liftEqDefault,
Matchable'(), genericZipMatchWith,
) where
import Control.Applicative
import Data.Functor.Classes
import Data.Maybe (fromMaybe, isJust)
import Data.Foldable
import Data.Functor.Identity
import Data.Functor.Compose
import Data.Functor.Product
import Data.Functor.Sum
import Data.Tagged
import Data.Proxy
import Data.List.NonEmpty (NonEmpty)
import Data.Map.Lazy (Map)
import qualified Data.Map.Lazy as Map
import Data.IntMap.Lazy (IntMap)
import qualified Data.IntMap.Merge.Lazy as IntMap
import Data.Tree (Tree)
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
import Data.Vector (Vector)
import qualified Data.Vector as Vector
import Data.Hashable (Hashable)
import Data.HashMap.Lazy (HashMap)
import qualified Data.HashMap.Lazy as HashMap
import GHC.Generics
class (Eq1 t, Functor t) => Matchable t where
zipMatch :: t a -> t b -> Maybe (t (a,b))
zipMatch = zipMatchWith (curry Just)
zipMatchWith :: (a -> b -> Maybe c) -> t a -> t b -> Maybe (t c)
{-# MINIMAL zipMatchWith #-}
zipzipMatch
:: (Matchable t, Matchable u)
=> t (u a)
-> t (u b)
-> Maybe (t (u (a, b)))
zipzipMatch = zipMatchWith zipMatch
fmapRecovered :: (Matchable t) => (a -> b) -> t a -> t b
fmapRecovered f ta =
fromMaybe (error "Law-violating Matchable instance") $
zipMatchWith (\a _ -> Just (f a)) ta ta
eqDefault :: (Matchable t, Eq a) => t a -> t a -> Bool
eqDefault = liftEqDefault (==)
liftEqDefault :: (Matchable t) => (a -> b -> Bool) -> t a -> t b -> Bool
liftEqDefault eq tx ty =
let u x y = if x `eq` y then Just () else Nothing
in isJust $ zipMatchWith u tx ty
instance Matchable Identity where
zipMatchWith = genericZipMatchWith
instance (Eq k) => Matchable (Const k) where
zipMatchWith = genericZipMatchWith
instance (Matchable f, Matchable g) => Matchable (Product f g) where
zipMatchWith = genericZipMatchWith
instance (Matchable f, Matchable g) => Matchable (Sum f g) where
zipMatchWith = genericZipMatchWith
instance (Matchable f, Matchable g) => Matchable (Compose f g) where
zipMatchWith = genericZipMatchWith
instance Matchable Proxy where
zipMatchWith _ _ _ = Just Proxy
instance Matchable (Tagged t) where
zipMatchWith = genericZipMatchWith
instance Matchable Maybe where
zipMatchWith = genericZipMatchWith
instance Matchable [] where
zipMatchWith = genericZipMatchWith
instance Matchable NonEmpty where
zipMatchWith = genericZipMatchWith
instance (Eq e) => Matchable ((,) e) where
zipMatchWith = genericZipMatchWith
instance (Eq e) => Matchable (Either e) where
zipMatchWith = genericZipMatchWith
instance (Eq k) => Matchable (Map k) where
zipMatchWith u ma mb =
Map.fromAscList <$> zipMatchWith (zipMatchWith u) (Map.toAscList ma) (Map.toAscList mb)
instance Matchable IntMap where
zipMatchWith u =
IntMap.mergeA (IntMap.traverseMissing (\_ _ -> Nothing))
(IntMap.traverseMissing (\_ _ -> Nothing))
(IntMap.zipWithAMatched (const u))
instance Matchable Tree where
zipMatchWith = genericZipMatchWith
instance Matchable Seq where
zipMatch as bs
| Seq.length as == Seq.length bs = Just (Seq.zip as bs)
| otherwise = Nothing
zipMatchWith u as bs
| Seq.length as == Seq.length bs = unsafeFillIn u as (Data.Foldable.toList bs)
| otherwise = Nothing
instance Matchable Vector where
zipMatch as bs
| Vector.length as == Vector.length bs = Just (Vector.zip as bs)
| otherwise = Nothing
zipMatchWith u as bs
| Vector.length as == Vector.length bs = Vector.zipWithM u as bs
| otherwise = Nothing
instance (Eq k, Hashable k) => Matchable (HashMap k) where
zipMatch as bs
| HashMap.size as == HashMap.size bs =
HashMap.traverseWithKey (\k a -> (,) a <$> HashMap.lookup k bs) as
| otherwise = Nothing
zipMatchWith u as bs
| HashMap.size as == HashMap.size bs =
HashMap.traverseWithKey (\k a -> u a =<< HashMap.lookup k bs) as
| otherwise = Nothing
class Matchable' t where
zipMatchWith' :: (a -> b -> Maybe c) -> t a -> t b -> Maybe (t c)
genericZipMatchWith
:: (Generic1 t, Matchable' (Rep1 t))
=> (a -> b -> Maybe c)
-> t a
-> t b
-> Maybe (t c)
genericZipMatchWith u ta tb = to1 <$> zipMatchWith' u (from1 ta) (from1 tb)
{-# INLINABLE genericZipMatchWith #-}
instance Matchable' V1 where
{-# INLINABLE zipMatchWith' #-}
zipMatchWith' _ a _ = case a of { }
instance Matchable' U1 where
{-# INLINABLE zipMatchWith' #-}
zipMatchWith' _ _ _ = pure U1
instance Matchable' Par1 where
{-# INLINABLE zipMatchWith' #-}
zipMatchWith' u (Par1 a) (Par1 b) = Par1 <$> u a b
instance Matchable f => Matchable' (Rec1 f) where
{-# INLINABLE zipMatchWith' #-}
zipMatchWith' u (Rec1 fa) (Rec1 fb) = Rec1 <$> zipMatchWith u fa fb
instance (Eq c) => Matchable' (K1 i c) where
{-# INLINABLE zipMatchWith' #-}
zipMatchWith' _ (K1 ca) (K1 cb)
= if ca == cb then pure (K1 ca) else empty
instance Matchable' f => Matchable' (M1 i c f) where
{-# INLINABLE zipMatchWith' #-}
zipMatchWith' u (M1 fa) (M1 fb) = M1 <$> zipMatchWith' u fa fb
instance (Matchable' f, Matchable' g) => Matchable' (f :+: g) where
{-# INLINABLE zipMatchWith' #-}
zipMatchWith' u (L1 fa) (L1 fb) = L1 <$> zipMatchWith' u fa fb
zipMatchWith' u (R1 ga) (R1 gb) = R1 <$> zipMatchWith' u ga gb
zipMatchWith' _ _ _ = empty
instance (Matchable' f, Matchable' g) => Matchable' (f :*: g) where
{-# INLINABLE zipMatchWith' #-}
zipMatchWith' u (fa :*: ga) (fb :*: gb) =
liftA2 (:*:) (zipMatchWith' u fa fb) (zipMatchWith' u ga gb)
instance (Matchable f, Matchable' g) => Matchable' (f :.: g) where
{-# INLINABLE zipMatchWith' #-}
zipMatchWith' u (Comp1 fga) (Comp1 fgb) =
Comp1 <$> zipMatchWith (zipMatchWith' u) fga fgb
unsafeFillIn :: (Traversable f) => (a -> b -> Maybe c) -> f a -> [b] -> Maybe (f c)
unsafeFillIn u as bs = fst <$> runFillIn (traverse (useOne u) as) bs
newtype FillIn b a = FillIn { runFillIn :: [b] -> Maybe (a, [b]) }
deriving (Functor)
instance Applicative (FillIn b) where
pure a = FillIn $ \bs -> Just (a, bs)
FillIn fx <*> FillIn fy = FillIn $ \bs ->
fx bs >>= \(x, bs') ->
fy bs' >>= \(y, bs'') -> Just (x y, bs'')
useOne :: (a -> b -> Maybe c) -> a -> FillIn b c
useOne u a = FillIn $ \bs -> case bs of
[] -> Nothing
(b:bs') -> u a b >>= \c -> Just (c, bs')