{-# LANGUAGE TypeOperators #-} module Data.Iso.Core ( -- * Partial isomorphisms Iso(..), convert, inverse, many, -- * Stack-based isomorphisms (:-)(..), stack, unstack, swap, duck, lit, inverseLit, matchWithDefault, ignoreWithDefault ) where import Prelude hiding (id, (.), head) import Data.Monoid import Data.Semigroup import Control.Applicative hiding (many) import Control.Monad import Control.Category -- Partial isomorphisms -- | Bidirectional partial isomorphism. data Iso a b = Iso (a -> Maybe b) (b -> Maybe a) instance Category Iso where id = Iso Just Just ~(Iso f1 g1) . ~(Iso f2 g2) = Iso (f1 <=< f2) (g1 >=> g2) instance Monoid (Iso a b) where mempty = Iso (const Nothing) (const Nothing) ~(Iso f1 g1) `mappend` ~(Iso f2 g2) = Iso ((<|>) <$> f1 <*> f2) ((<|>) <$> g1 <*> g2) instance Semigroup (Iso a b) where (<>) = mappend -- | Apply an isomorphism in one direction. convert :: Iso a b -> a -> Maybe b convert (Iso f _) = f -- | Inverse of an isomorphism. inverse :: Iso a b -> Iso b a inverse (Iso f g) = Iso g f -- | Apply an isomorphism as many times as possible, greedily. many :: Iso a a -> Iso a a many (Iso f g) = Iso manyF manyG where manyF = ((<|>) <$> (f >=> manyF) <*> Just) manyG = ((<|>) <$> (g >=> manyG) <*> Just) -- Stack-based isomorphisms -- | Heterogenous stack with a head and a tail. data h :- t = h :- t deriving (Eq, Show) infixr 5 :- head :: (h :- t) -> h head (h :- _) = h -- | Convert to a stack isomorphism. stack :: Iso a b -> Iso (a :- t) (b :- t) stack (Iso f g) = Iso (lift f) (lift g) where lift k (x :- t) = (:- t) <$> k x -- | Convert from a stack isomorphism. unstack :: Iso (a :- ()) (b :- ()) -> Iso a b unstack (Iso f g) = Iso (lift f) (lift g) where lift k = fmap head . k . (:- ()) -- | Swap the top two arguments. swap :: Iso (a :- b :- t) (b :- a :- t) swap = Iso f f where f (x :- y :- t) = Just (y :- x :- t) -- | Introduce a head value that is passed unmodified. duck :: Iso t1 t2 -> Iso (h :- t1) (h :- t2) duck (Iso f g) = Iso (lift f) (lift g) where lift k (h :- t) = (h :-) <$> k t -- | Push or pop a specific value. lit :: Eq a => a -> Iso t (a :- t) lit x = Iso f g where f t = Just (x :- t) g (x' :- t) = do guard (x' == x) Just t -- | Inverse of 'lit'. inverseLit :: Eq a => a -> Iso (a :- t) t inverseLit = inverse . lit -- | When converting from left to right, push the default value on top of the -- stack. When converting from right to left, pop the value, make sure it -- matches the predicate and then discard it. matchWithDefault :: (a -> Bool) -> a -> Iso t (a :- t) matchWithDefault p x = Iso f g where f t = Just (x :- t) g (x' :- t) = do guard (p x') return t -- | When converting from left to right, push the default value on top of the stack. When converting from right to left, pop the value and discard it. ignoreWithDefault :: a -> Iso t (a :- t) ignoreWithDefault = matchWithDefault (const True)