yall-0.2: Data/Yall/Iso.hs
{-# LANGUAGE TypeOperators , MultiParamTypeClasses , FlexibleInstances, FlexibleContexts, GeneralizedNewtypeDeriving , TypeFamilies #-}
module Data.Yall.Iso (
{- |
Iso is similar but more flexible than Lens in that they have no dependency on
context. This flexibility affords a number of nice class instances that we
don't get with Lens, so these can be quite useful in combination. See 'isoL'
for converting to 'Lens'.
A less imprecise name for the code here might be @Bijection@ but no one wants
to type that.
-}
Iso(..)
, inverseI
-- * Convenient Iso types
-- ** Pure isomorphisms
, (:<->)
, iso
, ($-), (-$)
-- *** Wrapped pure Iso
, IsoPure(..), ifmap, fromPure
-- *** Pre-defined isomorphisms
{- | Note: while all of these are pure and could be expressed as '(:<->)', we
define them polymorphically in @Monad@ for maximum flexibility in
composing with other @Lens@ or @Iso@.
Also note that for most of these @apply i . unapply i@ is not strictly
@id@ for the entire input domain, e.g. @zipI@ obviously truncates lists of
differing length, etc.
-}
, wordsI, showI, linesI, curryI, enumI, integerI, rationalI, zipI
, incrementI, incrementByI, consI
, distributeI, factorI
-- ** Partial isomorphisms
, (:<~>)
) where
import Prelude hiding ((.),id)
import Control.Category
import Data.Functor.Identity
import Control.Monad
-- from 'categories':
import qualified Control.Categorical.Functor as C
import Control.Categorical.Bifunctor
import Control.Category.Associative
import Control.Category.Braided
import Control.Category.Monoidal
import Control.Category.Distributive
-- | An Isomorphism or one-to-one mapping between types. These are very similar
-- to a 'Lens', but are not dependent on context, making them more flexible. The
-- functions also alow a Monadic context, supporting partial isomorphisms, and
-- other interesting functionality.
data Iso w m a b = Iso { apply :: a -> m b
, unapply :: b -> w a }
instance (Monad m, Monad w)=> Category (Iso w m) where
id = iso id id
g . f = Iso (apply f >=> apply g) (unapply g >=> unapply f)
-- | A wrapper for a more @(->)@-like Functor instances
newtype IsoPure a b = IsoPure { isoPure :: Iso Identity Identity a b }
deriving (Category)
-- ghetto deriving:
pureWrapped :: (Iso Identity Identity a1 b1 -> Iso Identity Identity a b)
-> IsoPure a1 b1
-> IsoPure a b
pureWrapped2 :: (Iso Identity Identity a1 b1
-> Iso Identity Identity a2 b2
-> Iso Identity Identity a b)
-> IsoPure a1 b1
-> IsoPure a2 b2
-> IsoPure a b
pureWrapped2 f a b = IsoPure $ f (isoPure a) (isoPure b)
pureWrapped f = IsoPure . f . isoPure
instance PFunctor (,) IsoPure IsoPure where
first = pureWrapped first
instance QFunctor (,) IsoPure IsoPure where
second = pureWrapped second
instance Bifunctor (,) IsoPure IsoPure IsoPure where
bimap = pureWrapped2 bimap
instance PFunctor Either IsoPure IsoPure where
first = pureWrapped first
instance QFunctor Either IsoPure IsoPure where
second = pureWrapped second
instance Bifunctor Either IsoPure IsoPure IsoPure where
bimap = pureWrapped2 bimap
instance Associative IsoPure (,) where
associate = IsoPure associate
instance Associative IsoPure Either where
associate = IsoPure associate
instance Disassociative IsoPure (,) where
disassociate = IsoPure disassociate
instance Disassociative IsoPure Either where
disassociate = IsoPure disassociate
instance Braided IsoPure (,) where
braid = IsoPure braid
instance Braided IsoPure Either where
braid = IsoPure braid
instance Symmetric IsoPure Either where
instance Symmetric IsoPure (,) where
type instance Id IsoPure (,) = ()
instance Monoidal IsoPure (,) where
idl = IsoPure idl
idr = IsoPure idr
instance Comonoidal IsoPure (,) where
coidl = IsoPure coidl
coidr = IsoPure coidr
-- | A more categorical 'fmap', with wrapping / unwrapping for convenience. See
-- also the 'C.Functor' instances for 'Iso'.
--
-- > ifmap = fromPure . C.fmap . IsoPure
ifmap :: (Monad w, Monad m, C.Functor f IsoPure IsoPure)=> Iso Identity Identity a b -> Iso w m (f a) (f b)
ifmap = fromPure . C.fmap . IsoPure
-- | Unwrap and make polymorphic an 'IsoPure'
fromPure :: (Monad w, Monad m)=> IsoPure a b -> Iso w m a b
fromPure (IsoPure (Iso f g)) = iso (fmap runIdentity f) (fmap runIdentity g)
-- Control.Categorical.Functor
instance (Functor f)=> C.Functor f IsoPure IsoPure where
fmap (IsoPure (Iso f g)) =
IsoPure $ iso (fmap $ fmap runIdentity f) (fmap $ fmap runIdentity g)
instance (Monad m)=> C.Functor m (Iso m m) (Iso Identity Identity) where
fmap (Iso f g) = iso (>>= f) (>>= g)
-- Control.Categorical.Bifunctor
instance (Monad m, Monad w)=> PFunctor (,) (Iso w m) (Iso w m) where
first = firstDefault
instance (Monad m, Monad w)=> QFunctor (,) (Iso w m) (Iso w m) where
second = secondDefault
instance (Monad m, Monad w)=> Bifunctor (,) (Iso w m) (Iso w m) (Iso w m) where
bimap (Iso f g) (Iso f' g') = Iso (bimapM f f') (bimapM' g g')
-- WHY DOES TypeFamilies CAUSE PROBLEMS WITH THIS?:
where bimapM x = fmap extractJoinT . bimap x
bimapM' x = fmap extractJoinT . bimap x
instance (Monad m, Monad w)=> PFunctor Either (Iso w m) (Iso w m) where
first = firstDefault
instance (Monad m, Monad w)=> QFunctor Either (Iso w m) (Iso w m) where
second = secondDefault
instance (Monad m, Monad w)=> Bifunctor Either (Iso w m) (Iso w m) (Iso w m) where
bimap (Iso f g) (Iso f' g') = Iso (bimapM f f') (bimapM' g g')
where bimapM x = fmap extractJoinE . bimap x
bimapM' x = fmap extractJoinE . bimap x
-- Does this already exist in Categories?
-- :: k (m a) (m b) -> m (k a b)
-- For k = Either / (,)
-- m = any Monad
extractJoinE :: (Monad m)=> Either (m a) (m b) -> m (Either a b)
extractJoinE = either (liftM Left) (liftM Right)
extractJoinT :: (Monad m)=> (m a, m b) -> m (a,b)
extractJoinT = uncurry $ liftM2 (,)
-- Control.Category.Associative
instance (Monad m, Monad w)=> Associative (Iso w m) (,) where
associate = iso associate disassociate
instance (Monad m, Monad w)=> Associative (Iso w m) Either where
associate = iso associate disassociate
instance (Monad m, Monad w)=> Disassociative (Iso w m) (,) where
disassociate = iso disassociate associate
instance (Monad m, Monad w)=> Disassociative (Iso w m) Either where
disassociate = iso disassociate associate
-- Control.Category.Braided
instance (Monad m, Monad w)=> Braided (Iso w m) (,) where
braid = iso braid braid
instance (Monad m, Monad w)=> Braided (Iso w m) Either where
braid = iso braid braid
instance (Monad m, Monad w)=> Symmetric (Iso w m) (,) where
instance (Monad m, Monad w)=> Symmetric (Iso w m) Either where
distributeI :: (Monad m, Monad w)=> Iso w m (a, Either b c) (Either (a,b) (a,c))
distributeI = iso distribute factor
factorI :: (Monad m, Monad w)=> Iso w m (Either (a,b) (a,c)) (a, Either b c)
factorI = iso factor distribute
-- Control.Category.Monoidal
type instance Id (Iso w m) (,) = ()
instance (Monad m, Monad w)=> Monoidal (Iso w m) (,) where
idl = iso idl coidl
idr = iso idr coidr
instance (Monad m, Monad w)=> Comonoidal (Iso w m) (,) where
coidl = iso coidl idl
coidr = iso coidr idr
-- | See also an Iso wrapped in 'Dual'
inverseI :: (Monad m, Monad w)=> Iso w m a b -> Iso m w b a
inverseI (Iso f g) = Iso g f
-- | a partial Isomorphism
type a :<~> b = Iso Maybe Maybe a b
-- | pure Iso
type a :<-> b = Iso Identity Identity a b
iso :: (Monad m, Monad w)=> (a -> b) -> (b -> a) -> Iso w m a b
iso f g = Iso (fmap return f) (fmap return g)
-- | apply the forward function
--
-- > i $- a = runIdentity $ apply i a
($-) :: (a :<-> b) -> a -> b
i $- a = runIdentity $ apply i a
-- | apply the backward function
--
-- > i -$ b = runIdentity $ unapply i b
(-$) :: (a :<-> b) -> b -> a
i -$ b = runIdentity $ unapply i b
-------------
wordsI :: (Monad m, Monad w)=> Iso w m String [String]
wordsI = iso words unwords
linesI :: (Monad m, Monad w)=> Iso w m String [String]
linesI = iso lines unlines
showI :: (Read s, Show s, Monad w, Monad m)=> Iso w m s String
showI = iso show read
-- TODO or leave this as the instance above???
curryI :: (Monad m, Monad w)=> Iso w m ((a,b) -> c) (a -> b -> c)
curryI = iso curry uncurry
enumI :: (Enum a, Monad m, Monad w)=> Iso w m Int a
enumI = iso toEnum fromEnum
integerI :: (Integral a, Monad m, Monad w)=> Iso w m a Integer
integerI = iso toInteger fromInteger
rationalI :: (Real a, Fractional a, Monad m, Monad w)=> Iso w m a Rational
rationalI = iso toRational fromRational
zipI :: (Monad m, Monad w)=> Iso w m ([a],[b]) [(a,b)]
zipI = iso (uncurry zip) unzip
incrementI :: (Monad m, Monad w, Num a)=> Iso w m a a
incrementI = incrementByI 1
incrementByI :: (Monad m, Monad w, Num a)=> a -> Iso w m a a
incrementByI n = iso (+n) (subtract n)
-- | Calls 'fail' on the empty list.
consI :: (Monad m, Monad w)=> Iso w m (a,[a]) [a]
consI = Iso (\(a,as)-> return (a:as)) unconsI
where unconsI [] = fail "empty list"
unconsI (a:as) = return (a,as)