module Isomorphism ( Isomorphism(Isomorphism, iso, osi), identityI, composeI, (=.=), inverseI, mapI, pairI, (=|=), functionI, (=>=), isomorphismI, Wrap(wisom, wiso, wosi) ) where -- The type Isomorphism is a pair of functions that together form -- an isomorphism between two types. Isomorphisms will become useful -- as newtype value constructors proliferate in our code. For example, -- in Steele's notation, if m is a monad and p is a pseudomonad, then -- the type "(m & p) a" is -not- identical to the type "m (p a)", but -- merely isomorphic to it. data Isomorphism a b = Isomorphism { iso :: a -> b, osi :: b -> a } -- identityI is the identity isomorphism between a type and itself. identityI :: Isomorphism a a identityI = Isomorphism { iso = id, osi = id } -- composeI composes two isomorphisms. It can be written infix as (=.=). composeI :: Isomorphism b c -> Isomorphism a b -> Isomorphism a c composeI Isomorphism { iso = iso1, osi = osi1 } Isomorphism { iso = iso2, osi = osi2 } = Isomorphism { iso = iso1.iso2, osi = osi2.osi1 } infixr 9 `composeI`, =.= (=.=) = composeI -- inverseI inverts the two ends of an isomorphism. inverseI :: Isomorphism a b -> Isomorphism b a inverseI Isomorphism { iso = iso0, osi = osi0 } = Isomorphism { iso = osi0, osi = iso0 } -- mapI lifts an isomorphism through a functor. For example, it can -- convert an isomorphism from a to b into one from [a] to [b]. mapI :: (Functor f) => Isomorphism a b -> Isomorphism (f a) (f b) mapI Isomorphism { iso = iso0, osi = osi0 } = Isomorphism { iso = fmap iso0, osi = fmap osi0 } -- pairI combines two isomorphisms into an isomorphism between pairs -- (i.e., binary tuples). It can be written infix as (=|=). pairI :: Isomorphism a b -> Isomorphism c d -> Isomorphism (a,c) (b,d) pairI Isomorphism { iso = iso1, osi = osi1 } Isomorphism { iso = iso2, osi = osi2 } = Isomorphism { iso = \(x,y) -> (iso1 x, iso2 y) , osi = \(x,y) -> (osi1 x, osi2 y) } infix 9 `pairI`, =|= (=|=) = pairI -- functionI combines two isomorphisms into an isomorphism between -- functions. It can be written infix as (=>=). functionI :: Isomorphism a b -> Isomorphism c d -> Isomorphism (a->c) (b->d) functionI Isomorphism { iso = iso1, osi = osi1 } Isomorphism { iso = iso2, osi = osi2 } = Isomorphism { iso = \f -> iso2.f.osi1 , osi = \f -> osi2.f.iso1 } infixr 9 `functionI`, =>= (=>=) = functionI -- isomorphismI combines two isomorphisms into an isomorphism between -- isomorphisms. isomorphismI :: Isomorphism a a' -> Isomorphism b b' -> Isomorphism (Isomorphism a b) (Isomorphism a' b') isomorphismI isom1 isom2 = Isomorphism { iso = \isom -> isom2 =.= isom =.= inverseI isom1 , osi = \isom -> inverseI isom2 =.= isom =.= isom1 } -- Isomorphism between Haskell types are not easy to represent using -- Haskell type classes, even with multi-parameter type classes, because -- of restrictions on overlapping instances. We define here a type class -- "Wrap"; we will only use it to relate "wrapper types" (e.g., newtypes) -- and the types they wrap. -- Wrap is a multi-parameter type class; multi-parameter type classes are -- not part of the Haskell 98 standard. We use Wrap here for convenience -- only; it would be easy and only slightly complicate our notation to -- remove Wrap from our code. class Wrap a b where -- Minimal complete definition: wisom, or both wiso and wosi wisom :: Isomorphism a b wisom = Isomorphism { iso = wiso, osi = wosi } wiso :: a -> b wiso = iso wisom wosi :: b -> a wosi = osi wisom