--


import IOExts

-- Example datatypes
-- [], Either, Maybe, Bool
data Tree a b = Tip a | Node (Tree a b) b (Tree a b)

-- Example generic functions

equal :: Rep a -> a -> a -> Bool
display :: Rep a -> a -> String
marshall :: Rep a -> a -> [ Int ]
unmarshall :: Rep a -> [Int] -> a
----------------------------------------------------

data Rep t = Univ Name (t -> Val) (Val -> t)
           | Iso (Shape Iso t)
           | Leibniz (Shape Leibniz t)

data Name = App String [Name] deriving Eq

class Named t where
  name :: t -> Name

class Generic t where
  univ :: Rep t
  iso :: Rep t
  leib :: Rep t
  gmapT :: (forall b . Generic b => b -> b) -> t -> t
  gmapQ :: (forall b . Generic b => b -> r) -> t -> [r]
  gmapM :: Monad m => (forall b . Generic b => b -> m b) -> t -> m t
  gfoldl :: (forall a b . Generic a => w (a -> b) -> a -> w b) ->
            (forall g . g -> w g) ->
            t -> w t

class Generic1 t where
  univ1 :: Rep a -> Rep(t a)
  iso1 :: Rep a -> Rep(t a)
  leib1 :: Rep a -> Rep(t a)
  gmapT1 :: Generic a => (forall b . Generic b => b -> b) -> t a -> t a
  gmapQ1 :: Generic a => (forall b . Generic b => b -> r) -> t a -> [r]
  gmapM1 :: (Generic a,Monad m) => (forall b . Generic b => b -> m b) -> t a -> m (t a)
  gfoldl1 :: (Generic c) => (forall a b . Generic a => w (a -> b) -> a -> w b) ->
                            (forall g . g -> w g) ->
                            t c -> w (t c)

class Generic2 t where
  univ2 :: Rep a -> Rep b -> Rep (t a b)
  iso2 :: Rep a -> Rep b -> Rep (t a b)
  leib2 :: Rep a -> Rep b -> Rep (t a b)
  gmapT2 ::(Generic a,Generic c) => (forall b . Generic b => b -> b) -> t a c -> t a c
  gmapQ2 :: (Generic a,Generic c) => (forall b . Generic b => b -> r) -> t a c -> [r]
  gmapM2 :: (Generic a,Generic c,Monad m) => (forall b . Generic b => b -> m b) -> t a c -> m (t a c)
  gfoldl2 :: (Generic c,Generic d) =>
             (forall a b . Generic a => w (a -> b) -> a -> w b) ->
             (forall g . g -> w g) ->
             t c d -> w (t c d)

-----------------------------------------------------------------
-- Example use of Name

intN = App "Int" []
charN = App "Char" []
unitN = App "()" []

instance Named (Rep a) where
  name (Univ nm f g) = nm
  name (Iso s) = name s
  name (Leibniz s) = name s


-----------------------------------------------------------------
-- The Univ approach

into (Univ nm f g) = f
out  (Univ nm f g) = g

data Val
  = Vint Int
  | Vchar Char
  | Vunit
  | Vfun (Val  -> Val )
  | Vdata String [Val]
  | Vtuple [Val ]
  | Vpar Int Val

mapVal :: (Val -> Val) -> Val -> Val
mapVal f (Vpar n a) = Vpar n(f a)
mapVal f (Vint n) = Vint n
mapVal f (Vchar c) = Vchar c
mapVal f Vunit = Vunit
mapVal f (Vfun h) = error "can't mapVal Vfun"
mapVal f (Vdata s xs) = Vdata s (map (mapVal f) xs)
mapVal f (Vtuple xs) = Vtuple(map (mapVal f) xs)

instance Num Val where
  fromInt x = Vint x
  (+) (Vint x) (Vint y) = Vint (x+y)
  (*) (Vint x) (Vint y) = Vint (x*y)
  (-) (Vint x) (Vint y) = Vint (x - y)

instance Generic Val where
  univ = Univ (App "Val" []) id id

instance Show (Val ) where
   show (Vint n) = show n
   show (Vchar c) = show c
   show Vunit = "()"
   show (Vfun f) = "fn"
   show (Vdata s []) = s
   show (Vdata s xs) = "("++s++plist " " xs " " ")"
   show (Vtuple xs) = plist "(" xs "," ")"
   show (Vpar n x) = show x


plist :: Show a => String -> [a] -> String -> String -> String
plist open xs sep close = open ++ help xs ++ close
  where help [] = ""
        help [x] = show x
        help (x:xs) = show x ++ sep ++ help xs

instance Eq Val where
  x == y = test x y
    where test (Vint n) (Vint m) = n==m
          test (Vchar n) (Vchar m) = n==m
          test Vunit Vunit = True
          test (Vdata s xs) (Vdata t ys) = s==t && tests xs ys
          test (Vtuple xs) (Vtuple ys) = tests xs ys
          test (Vpar n x) (Vpar m y) = test x y
          test _ _ = False
          tests [] [] = True
          tests (x:xs) (y:ys) = test x y && tests xs ys
          tests _ _ = False

-----------------------------------------------------------------
-- Examples of Universal Representations

intU =  Univ intN Vint (\ (Vint n) -> n)
charU = Univ charN Vchar (\ (Vchar c) -> c)
unitU = Univ unitN (const Vunit) (const ())

pairU :: (Rep a) -> (Rep b) -> Rep (a,b)
pairU (Univ a to1 from1) (Univ b to2 from2)  = Univ (App "(,)" [a,b]) f g
   where f (x,y) = Vtuple[to1 x,to2 y]
         g (Vtuple[x,y])= (from1 x,from2 y)
         g y = error ("In g "++(show y))

arrowU :: Rep a -> Rep b -> Rep (a -> b)
arrowU (r1 @ (Univ a _ _)) (r2 @ (Univ b _ _)) =  Univ (App "->" [a,b]) f g
  where f h = Vfun(into r2 . h . out r1)
        g (Vfun h) = out r2 . h . into r1

listU :: Rep a -> Rep [a]
listU (Univ a to from) = Univ (App "[]" [a]) h k
  where h [] = Vdata "[]" []
        h (x:xs) = Vdata ":" [ Vpar 1 (to x),h xs]
        k (Vdata "[]" []) = []
        k (Vdata ":" [Vpar 1 x,xs]) = (from x) : k xs

plusU :: Rep a -> Rep b -> Rep (Either a b)
plusU (Univ a to1 from1) (Univ b to2 from2) = Univ (App "Either" [a,b]) h k
  where
   h (Left x) = Vdata "Left" [Vpar 1 (to1 x)]
   h (Right x) = Vdata "Right" [Vpar 2 (to2 x)]
   k (Vdata "Left" [Vpar 1 x]) = Left (from1 x)
   k (Vdata "Right" [Vpar 2 x]) = Right (from2 x)

boolU :: Rep Bool
boolU = Univ (App "Bool" []) f g
  where f True = Vdata "True" []
        f False = Vdata "False" []
        g (Vdata "True" []) = True
        g (Vdata "False" []) = False

maybeU :: Rep a -> Rep (Maybe a)
maybeU (Univ a to1 from1) = Univ (App "Maybe" [a]) h k
  where
   h Nothing = Vdata "Nothing" []
   h (Just x) = Vdata "Just" [Vpar 1 (to1 x)]
   k (Vdata "Nothing" []) = Nothing
   k (Vdata "Just" [Vpar 1 x]) = Just (from1 x)

-----------------------------------------------------------
-- Generic instances

instance Generic Int where
  univ = intU
  iso =  intI
  leib = intL
  gmapT f x = x
  gmapQ f x = []
  gmapM f x = return x
  gfoldl k z x = z x

instance Generic Char where
  univ = charU
  iso = charI
  leib = charL
  gmapT f x = x
  gmapQ f x = []
  gmapM f x = return x
  gfoldl k z x = z x

instance Generic () where
  univ = unitU
  iso = unitI
  leib = unitL
  gmapT f x = x
  gmapQ f x = []
  gmapM f x = return x
  gfoldl k z x = z x

instance (Generic1 t,Generic a) => Generic(t a) where
  univ = univ1 univ
  iso = univ1 iso
  leib = leib1 leib
  gmapT f = gmapT1 f
  gmapQ f = gmapQ1 f
  gmapM f = gmapM1 f
  gfoldl k z x = gfoldl1 k z x

instance Generic a => Generic1 ((,) a) where
  univ1 = pairU univ
  iso1 = pairI iso
  leib1 = pairL leib
  gmapT1 f (x,y) = (f x,f y)
  gmapQ1 f (x,y) = [f x,f y]
  gmapM1 f (x,y) = do { a <- f x; b <- f y; return(x,y) }
  gfoldl1 k z (x,y) = (z g `k` x) `k` y where g a b = (a,b)

instance Generic a => Generic1 ((->) a) where
  univ1 = arrow univ
  iso1 = funI iso
  leib1 = funL leib
  gmapT1 f x = error "No Strafunski gmapT on function types"
  gmapQ1 f x = error "No Strafunski gmapQ on function types"
  gmapM1 f x = error "No Strafunski gmapM on function types"
  gfoldl1 f x = error "No Strafunski gfoldl on function types"

instance Generic Bool where
  univ = boolU
  iso = Iso boolP
  leib = Leibniz boolP
  gmapT f True = True
  gmapT f False = False
  gmapQ f True = []
  gmapQ f False = []
  gmapM f True = return True
  gmapM f False = return False
  gfoldl k z True = z True
  gfoldl k z False = z False

instance Generic a => Generic1 (Either a) where
  univ1 = plusU univ
  iso1 = plusI iso
  leib1 = plusL leib
  gmapT1 f (Left x) = Left(f x)
  gmapT1 f (Right x) = Right(f x)
  gmapQ1 f (Left x) = [f x]
  gmapQ1 f (Right x) = [f x]
  gmapM1 f (Left x) = do { a <- f x; return(Left a)}
  gmapM1 f (Right x) = do { a <- f x; return(Right a)}
  gfoldl1 k z (Left x) = z Left `k` x
  gfoldl1 k z (Right x) = (z Right `k` x)

instance Generic1 [] where
  univ1 = list
  iso1 (Iso s) = Iso(listP s)
  leib1 (Leibniz s) = Leibniz(listP s)
  gmapT1 f [] = []
  gmapT1 f (x:xs) = (f x) : (f xs)
  gmapQ1 f [] = []
  gmapQ1 f (x:xs) = [f x,f xs]
  gmapM1 f [] = return []
  gmapM1 f (x:xs) = do { a <- f x; b <- f xs; return(a:b)}
  gfoldl1 k z [] = z []
  gfoldl1 k z (x:xs) = k (k (z (:)) x) xs

instance Generic1 Maybe where
  univ1 = maybeU
  iso1 (Iso s) = Iso(maybeP s)
  leib1 (Leibniz s) = Leibniz(maybeP s)
  gmapT1 f Nothing = Nothing
  gmapT1 f (Just x) = Just(f x)
  gmapQ1 f Nothing = []
  gmapQ1 f (Just x)= [f x]
  gmapM1 f Nothing = return Nothing
  gmapM1 f (Just x) = do { a <- f x; return(Just a) }
  gfoldl1 k z Nothing = z Nothing
  gfoldl1 k z (Just x) = k (z Just) x

lift x = into univ x

x,y,z::Int
x=0
y=1
z=2

----------------------------------------------------------
-- Generic functions using the universal approach
----------------------------------------------------------

gmap :: Rep b -> Rep c -> (forall a . Rep a -> Rep(t a)) -> (b->c) -> t b -> t c
gmap repB repC t f x = out repLC (help (into repLB x))
  where repLB = t repB
        repLC = t repC
        help xs = mapVal trans xs
        trans x = into repC (f(out repB x))

hmap :: (Generic a, Generic b, Generic1 c) => (a -> b) -> c a -> c b
hmap f x = gmap univ univ univ1 f x

disp :: Generic a => a -> [Char]
disp x = display univ x

eq :: Generic a => a -> a -> Bool
eq x y = equal univ x y

marsh :: Generic a => a -> [Int]
marsh x = marshall univ x

unmarsh :: Generic a => [Int] -> a
unmarsh x = unmarshall univ x


----------------------------------------------------------

equal (Univ nm to from) x y  =  (to x) == (to y)
equal (Iso sh) x y = rEqual sh x y
equal (Leibniz sh) x y = rEqual sh x y


display (Univ nm to from) x = show (to x)
display (Iso shp) x = rShow shp x
display (Leibniz shp) x = rShow shp x

marshall (Univ nm to from) x = reverse (flat (to x) [])

unmarshall (Univ nm to from) xs = from j
  where (j,ks) = (unflat xs)

flat :: Val -> [Int] -> [Int]
flat (Vint n) xs = n : 1 : xs
flat (Vchar c) xs = ord c : 2 : xs
flat Vunit xs = 3: xs
flat (Vfun f) xs = error "no Vfun in marshall"
flat (Vdata s zs) xs = flatList zs (length zs : (flatString s (5: xs)))
flat (Vtuple zs) xs = flatList zs (length zs : 6 : xs)
flat (Vpar n x) xs = flat x (n : 7 : xs)

flatList [] xs = xs
flatList (z:zs) xs = flatList zs (flat z xs)

unflat :: [Int] -> (Val,[Int])
unflat (1: x : xs) = (Vint x,xs)
unflat (2: x : xs) = (Vchar (chr x),xs)
unflat (3: xs) = (Vunit,xs)
unflat (5: xs) = (Vdata s ws,zs)
    where (s,n : ys) = unflatString xs
          (ws,zs) = unflatList n ys
unflat (6: n : xs) = (Vtuple ws,zs) where (ws,zs) = unflatList n xs
unflat (7: n: xs) = (Vpar n x,ys) where (x,ys) = unflat xs
unflat zs = error ("Bad Case in unflat of unmarshall"++ show zs)

unflatList 0 xs = ([],xs)
unflatList n xs = (v:vs,zs)
  where (v,ys)= unflat xs
        (vs,zs) = unflatList (n-1) ys

flatString s xs = (reverse (map ord s)) ++ ((length s) : xs)
unflatString (n:xs) = (map chr (take n xs),drop n xs)

------------------------------------------------
-- The Shape approach

--data Rep t = Univ Name (t -> Val) (Val -> t)
--           | Iso (Shape Iso t)
--           | Leibniz (Shape Leibniz t)

data Shape eq t
  = Sint (eq t Int)
  | Schar (eq t Char)
  | Sunit (eq t ())
  | forall a b . Sfun (Shape eq a) (Shape eq b) (eq t (a->b))
  | forall a b . Spair (Shape eq a) (Shape eq b) (eq t (a,b))
  | forall a b . Splus (Shape eq a) (Shape eq b) (eq t (Either a b))
  | forall a   . Sdata Name (Shape eq t -> Shape eq a) (Iso t a)
  |              Scon String (Shape eq t)
  | Sptr Name (Ptr t)

instance Named (Shape p t) where
	--name :: Shape p t -> Name
	name (Sint p) = App "Int" []
	name (Schar p) = App "Char" []
	name (Sunit p) = App "()" []
	name (Sfun x y p) = App "->" [name x, name y]
	name (Spair x y p) = App "(,)" [name x, name y]
	name (Splus x y p) = App "Either" [name x, name y]
	name (Sdata t x y) = t
	name (Scon s t) = name t
	name (Sptr nm p) = nm

instance Show Name where
  show (App "->" [x @ (App "->" _),y]) =   "("++(show x) ++") -> "++(show y)
  show (App "->" [x,y]) =   (show x) ++" -> "++(show y)
  show (App "(,)" [x,y]) = "("++show x++","++show y++")"
  show (App s []) = s
  show (App s ts) = s ++ " " ++ f ts
    where f [t] = show t
          f (t : ts) = show t ++ " " ++ f ts

----------------------------------------------------------
-- Proofs of equality

class EqProof proof where
  from :: proof a b -> (a -> b)
  to   :: proof a b -> (b -> a)
  self :: proof a a
  inv  :: proof a b -> proof b a
  assoc:: proof a b -> proof b c -> proof a c
  (.+.) :: proof a b -> proof c d -> proof (Either a c) (Either b d)
  (.*.) :: proof a b -> proof c d -> proof (a,c) (b,d)
  (.->.) :: proof a b -> proof c d -> proof (a -> c) (b -> d)
  testEq :: Rep a -> Rep b -> Maybe(proof a b)

f <> g = assoc f g

intP :: EqProof p => Shape p Int
intP = (Sint self)

charP :: EqProof p => Shape p Char
charP = (Schar self)

unitP :: EqProof p => Shape p ()
unitP = (Sunit self)

pairP :: EqProof p => Shape p a -> Shape p b -> Shape p (a,b)
pairP x y = (Spair x y self)

plusP :: EqProof p => Shape p a -> Shape p b -> Shape p (Either a b)
plusP x y = (Splus x y self)

funP :: EqProof p => Shape p a -> Shape p b -> Shape p (a -> b)
funP  x y = (Sfun x y self)

listP :: EqProof p => Shape p a -> Shape p [a]
listP x = Sdata (App "[]" [name x]) intershape (Ep f g)
  where intershape a = plusP unitP (pairP x a)
        f [] = Left ()
        f (x:xs) = Right(x,xs)
        g (Left ()) = []
        g (Right(x,xs)) = x:xs

boolP :: EqProof p => Shape p Bool
boolP = Sdata (App "Bool" [])  struct proof
     where proof = Ep g f
           f (Left _) = True
           f (Right _) = False
           g True = Left ()
           g False = Right ()
           struct x = (plusP unitP unitP)

maybeP :: EqProof p => Shape p a -> Shape p (Maybe a)
maybeP x = Sdata (App "Maybe" [name x])  intershape (Ep f g)
   where intershape rec = plusP unitP x
         f Nothing = Left ()
         f (Just x) = Right x
         g (Left ()) = Nothing
         g (Right x) = Just x

-------------------------------------
-- Iso proofs

data Iso a b = Ep (a->b) (b->a)

instance EqProof Iso where
  from (Ep f g) = f
  to   (Ep f g) = g
  self = Ep id id
  inv f = Ep (to f) (from f)
  assoc f g = Ep (from g . from f) (to f . to g)
  (.+.) = isoE
  (.*.) = isoPair
  (.->.) = isoArrow
  testEq (Iso x) (Iso y) = unify x y


isoE :: (Iso a b) -> (Iso c d) -> Iso (Either a c) (Either b d)
isoE pab pcd = Ep (from pab + from pcd) (to pab + to pcd)
  where (+) f g (Left x)  = Left (f x)
        (+) f g (Right x) = Right(g x)

isoPair :: (Iso a b) -> (Iso c d) -> Iso (a,c) (b,d)
isoPair pab pcd = Ep (from pab * from pcd) (to pab * to pcd)
  where (*) f g (x,y)  = (f x,g y)

isoArrow :: (Iso a b) -> (Iso c d) -> Iso (a -> c) (b -> d)
isoArrow pab pcd = Ep (to pab $-> from pcd) (from pab $-> to pcd)
   where (f $-> g) h =  g . h . f

liftI f (Iso x) (Iso y) = Iso(f x y)

intI :: Rep Int
intI = Iso intP

charI :: Rep Char
charI = Iso charP

unitI :: Rep ()
unitI = Iso unitP

pairI :: Rep a -> Rep b -> Rep (a,b)
pairI = liftI pairP

plusI :: Rep a -> Rep b -> Rep (Either a b)
plusI = liftI plusP

funI :: Rep a -> Rep b -> Rep (a -> b)
funI  = liftI funP

boolI = Iso boolP

{-
f :: Rep a -> Rep b -> (forall x . Rep x -> Rep (t x)) -> (a -> b) -> t a -> t b
f ra rb t g x = walk shta shtb x
  where (Iso shta) = t ra
        (Iso shtb) = t rb
        walk :: (Shape Iso (t a)) -> (Shape Iso (t b)) -> t a -> t b
        walk (Sint p1) (Sint p2) x = to p2 (from p1 x)
        walk (Schar p1) (Schar p2) x = to p2 (from p1 x)
        walk (Sunit p1) (Sunit p2) x = to p2 (from p1 x)
        walk (Spair a b p1) (Spair m n p2) x =
          case from p1 x of
            (i,j) -> to p2 (walk a m i,walk b n j)
-}

-----------------------------------------------------
-- Leibniz proofs

data Leibniz a b = Eq { eq1 :: forall f. f a -> f b }

instance EqProof Leibniz where
  to e = unId . eq1 (inv e) . Id
  from e = unId . eq1 e . Id
  self = Eq id
  inv = flip eq2 self
  assoc = flip eq1
  (.+.) = congruence2
  (.*.) = congruence2
  (.->.) = congruence2
  testEq (Leibniz x) (Leibniz y) = unify x y

newtype Id a = Id { unId :: a}
newtype Flip f a b = Flip { unFlip :: f b a }
eq2 :: Leibniz a b -> f a c -> f b c
eq2 e = unFlip . eq1 e . Flip


liftL f (Leibniz x) (Leibniz y) = Leibniz(f x y)

intL :: Rep Int
charL :: Rep Char
unitL :: Rep ()

intL = Leibniz intP
charL = Leibniz charP
unitL = Leibniz unitP

pairL :: Rep a -> Rep b -> Rep (a,b)
plusL :: Rep a -> Rep b -> Rep (Either a b)
funL :: Rep a -> Rep b -> Rep (a -> b)

pairL = liftL pairP
plusL = liftL plusP
funL  = liftL funP

boolL = Leibniz boolP

-----------------------------------------------------------------
-- Putting it all together

pair x y =
  case (x,y) of
    (Leibniz _ ,Leibniz _ ) -> pairL x y
    (Iso _,Iso _) -> pairI x y
    (Univ _ _ _,Univ _ _ _) -> pairU x y

arrow x y =
  case (x,y) of
   (Leibniz _ ,Leibniz _ ) -> funL x y
   (Iso _,Iso _) -> funI x y
   (Univ _ _ _,Univ _ _ _) -> arrowU x y

plus x y =
  case (x,y) of
    (Leibniz _ ,Leibniz _ ) -> plusL x y
    (Iso _,Iso _) -> plusI x y
    (Univ _ _ _,Univ _ _ _) -> plusU x y

list (x @ (Univ _ _ _)) = listU x
list (x @ (Iso s))      = Iso(listP s)
list (x @ (Leibniz s))  = Leibniz(listP s)

maybeR (x @ (Univ _ _ _)) = maybeU x
maybeR (x @ (Iso s))      = Iso(maybeP s)
maybeR (x @ (Leibniz s))  = Leibniz(maybeP s)



-----------------------------------------------------------------

rEqual :: EqProof p => Shape p a -> a -> a -> Bool
rEqual (Sint ep) x y  =  from ep x == from ep y
rEqual (Schar ep) x y = from ep x == from ep y
rEqual (Sunit ep) x y  = from ep x == from ep y
rEqual (Spair a b ep) x y =
   case (from ep x,from ep y) of
      ((m1,n1),(m2,n2)) -> rEqual a m1 m2 && rEqual b n1 n2
rEqual (Splus a b ep) x y =
   case (from ep x,from ep y) of
      (Left m,Left n) -> rEqual a m n
      (Right m,Right n) -> rEqual b m n
      (_,_) -> False
rEqual (r @ (Sdata trm inter p)) x y = rEqual (inter r) (from p x) (from p y)
rEqual (Scon c s) x y = rEqual s x y
rEqual (Sptr nm p) x y = error "XXX"

rShow :: EqProof p => Shape p a -> a -> String
rShow (Sint p) x = show (from p x)
rShow (Schar p) c = show (from p c)
rShow (Sunit p) x = "()"
rShow (Spair a b p) x = "("++rShow a m++","++rShow b n++")"
   where (m,n) = from p x
rShow (Splus a b p) x =
   case (from p x) of
     Left x -> "(Left "++rShow a x++")"
     Right x -> "(Right "++rShow b x++")"
rShow (r @ (Sdata trm inter p)) x = rShow (inter r) (from p x)
rShow (Scon s (Sunit p)) x = s -- Nullary constructor
rShow (Scon s shp) x = "("++s++" "++(rShow shp x)++")"
rShow (Sptr nm p) x = error "YYYY"

--------------------------------------------------------------------------
-- Strufunski style generic approach
{-
class Generic a where
  . . .
  gmapT :: (forall b . Generic b => b -> b) -> t -> t
  gmapQ :: (forall b . Generic b => b -> r) -> t -> [r]
  gmapM :: Monad m => (forall b . Generic b => b -> m b) -> t -> m t
-}

everywhere :: Generic a => (forall b . Generic b => b -> b) -> a -> a
everywhere f x = f (gmapT (everywhere f) x)

everything :: Generic a => (r -> r -> r) -> (forall b . Generic b => b -> r) -> a -> r
everything plus f x = foldl plus (f x) (gmapQ (everything plus f) x)

cast :: Rep a -> Rep b -> a -> Maybe b
cast (Leibniz r1) (Leibniz r2) a =
  case unify r1 r2 of
    Just p -> Just(from p a)
    Nothing -> Nothing
cast (Iso r1) (Iso r2) a =
  case unify r1 r2 of
    Just p -> Just(from p a)
    Nothing -> Nothing
cast (Univ n1 f1 g1) (Univ n2 f2 g2) a =
  if n1 == n2
     then Just(unsafeCoerce a)
     else Nothing

makeTrans :: Rep a -> Rep b -> (a -> a) -> (b -> b)
makeTrans r1 r2 f =
  case cast (arrow r1 r1) (arrow r2 r2) f of
    Just g -> g
    Nothing -> id

p1 :: Int -> Int
p1 x = x+1

f1 :: Generic a => (a -> a)
f1 = makeTrans leib leib p1

addTrans :: Rep a -> Rep b -> (a -> a) -> (b -> b) -> (b -> b)
addTrans r1 r2 f h =
  case cast (arrow r1 r1) (arrow r2 r2) f of
    Just g -> g
    Nothing -> h

f # g = addTrans leib leib f (makeTrans leib leib g)

mkT f = makeTrans leib leib f

-------------------------------------------
-- Some examples

test1 :: Generic a => a -> a
test1 xs = everywhere (mkT f325) xs

w:: Int
w= 3

f325 :: Int -> Int
f325 3 = 5
f325 x = x


mapg :: (Generic a,Generic1 t) => (a -> b) -> t a -> t b
mapg f x = error "XX"  --gfoldl k z x
  --where k c x = c ((f # mapg f) x)
  --      z x = x

--kk c x = c ((p1 # mapg p1) x)

-------------------------------------------------------------
type Equal a b = Leibniz a b

newtype H f a x = H (Equal (f a) (f x))

congruence1 :: Equal a b -> Equal (f a) (f b)
congruence1 (Eq ab) = case ab (H self) of
	H x -> x

newtype F f a c x            = F (Equal (f a c) (f x c))
newtype G f a c b x          = G (Equal (f a c) (f b x))

congruence2 :: Equal a b -> Equal c d -> Equal (f a c) (f b d)
congruence2 (Eq ab) (Eq cd) =
       case ab (F self) of
         F x -> case cd (G x)   of
                  G y -> y

unify :: EqProof p => Shape p c -> Shape p d -> Maybe (p c d)
unify (Sint p1) (Sint p2) = return(p1 <> inv p2)
unify (Schar p1)(Schar p2) = return(p1 <> inv p2)
unify (Sunit p1)(Sunit p2) = return(p1 <> inv p2)
unify (Sfun d1 c1 p1) (Sfun d2 c2 p2) =
    do deq <- unify d1 d2
       ceq <- unify c1 c2
       return(p1 <> (deq .->. ceq) <> inv p2)
unify (Spair a1 b1 p1) (Spair a2 b2 p2) =
	do aeq <- unify a1 a2
	   beq <- unify b1 b2
           return(p1 <> (aeq .*. beq) <> inv p2)
unify (Splus a1 b1 p1) (Splus a2 b2 p2) =
	do aeq <- unify a1 a2
	   beq <- unify b1 b2
	   return(p1 <> (aeq .+. beq) <> inv p2)
unify (Sdata t1 i1 p1) (Sdata t2 i2 p2) =
  let ptr = newPtr undefined
  in if t1 == t2
        then do { p4 <- unify (i1 (Sptr t1 ptr)) (i2 (Sptr t2 ptr))
                ; unify (Sptr t1 ptr) (Sptr t2 ptr) }
        else Nothing
unify (Sptr _ p1) (Sptr _ p2) = testPtr3 (X self) p1 p2
unify _ _ = Nothing


ex2 = testEq (leib :: Rep[Int]) (leib :: Rep[Int]) :: Maybe (Leibniz [Int] [Int])

------------------------------------------------------------
-- Pointers allow the creation of Proofs of equality
-- Since if x::Ptr a  and y::Ptr b and the pointers are equal,
-- then the things they point to must be the same, so the types of those
-- things I.e. "a" and "b", must also be equal. This is a way to create
-- proofs without looking at the things pointed to. Crucial for
-- creating recursive representations.

newtype Ptr a = Ptr (IORef a)
instance Eq (Ptr a) where
  (Ptr x) == (Ptr y) = x==y
instance Show (Ptr a) where
  show (Ptr x) = "Ptr ?"

newPtr :: a -> Ptr a
newPtr x = Ptr(unsafePerformIO(newIORef x))

data Self p a = X (p a a)

testPtr3 :: Self p a -> Ptr a -> Ptr b -> Maybe (p a b)
testPtr3 (X self) x y =
  if x == unsafeCoerce y
     then Just (unsafeCoerce self)
     else Nothing

primitive unsafeCoerce "primUnsafeCoerce" :: a -> b

----------------------------------------------------------
-- data type definitions for the example

data Company = C [Dept]
data Dept = D Nam Manager [SubUnit]
data SubUnit = PU Employee | DU Dept
data Employee = E Person Salary
data Person = P Nam Address
data Salary = S Int
type Manager = Employee
type Nam = String
type Address = String

genCom :: Company
genCom = C [D "Research" ralf [PU joost, PU marlow],
            D "Strategy" blair []]

ralf,joost,marlow,blair :: Employee

ralf = E (P "Ralf" "Amsterdam") (S 8000)
joost = E (P "Joost" "Amsterdam") (S 1000)
marlow = E (P "Marlow" "Cambridge") (S 2000)
blair = E (P "Blair" "London") (S 15000)

incS k (S s) = (S (s+k))

ex3 = everywhere (mkT (incS 5)) genCom

{-

ex3 =
C [D "Research" (E (P "Ralf" "Amsterdam") (S 8005))
     [PU (E (P "Joost" "Amsterdam") (S 1005)),
      PU (E (P "Marlow" "Cambridge") (S 2005))]
  ,D "Strategy" (E (P "Blair" "London") (S 15005))
     []]

-}

----------------------------------------------------------
-- Generic instances for the example data type definitions

instance Generic Company where
  leib = company
  gmapT f (C x) = C(f x)
  gmapQ f (C x) = [f x]
  gmapM f (C x) = do { a <- f x; return(C a) }

instance Generic Dept where
  leib = dept
  gmapT f (D x y z) = D (f x) (f y) (f z)
  gmapQ f (D x y z) = [f x,f y,f z]
  gmapM f (D x y z) = do { a <- f x; b <- f y; c <- f z; return(D a b c) }


instance Generic SubUnit where
  leib = subUnit
  gmapT f (PU x) = PU(f x)
  gmapT f (DU x) = DU(f x)
  gmapQ f (PU x) = [f x]
  gmapQ f (DU x) = [f x]
  gmapM f (PU x) = do { a <- f x; return(PU a) }
  gmapM f (DU x) = do { a <- f x; return(DU a) }

instance Generic Employee where
  leib = employee
  gmapT f (E x y) = E (f x) (f y)
  gmapQ f (E x y) = [f x,f y]
  gmapM f (E x y) = do { a <- f x; b <- f y; return(E a b) }


instance Generic Person where
  leib = person
  gmapT f (P x y) = P (f x) (f y)
  gmapQ f (P x y) = [f x,f y]
  gmapM f (P x y) = do { a <- f x; b <- f y; return(P a b) }


instance Generic Salary where
  leib = salary
  gmapT f (S x) = S(f x)
  gmapQ f (S x) = [f x]
  gmapM f (S x) = do { a <- f x; return(S a) }

companyS ::  EqProof p => Shape p Company
companyS = Sdata (App "Company" []) h (Ep f g)
  where h x = listP deptS
        f (C xs) = xs
        g xs = C xs

deptS ::  EqProof p => Shape p Dept
deptS = Sdata (App "Dept" []) h (Ep f g)
  where h x = pairP namexS (pairP managerS (listP subUnitS))
        f (D nm m ss) = (nm,(m,ss))
        g (nm,(m,ss)) = D nm m ss

subUnitS ::  EqProof p => Shape p SubUnit
subUnitS = Sdata (App "SubUnit" []) h (Ep f g)
  where h x = plusP employeeS deptS
        f (PU e) = Left e
        f (DU d) = Right d
        g (Left e) = PU e
        g (Right d) = DU d

employeeS ::  EqProof p => Shape p Employee
employeeS = Sdata (App "Employee" []) h (Ep f g)
  where h x = pairP personS salaryS
        f (E a b) = (a,b)
        g (a,b) = E a b

personS ::  EqProof p => Shape p Person
personS = Sdata (App "Person" []) h (Ep f g)
  where h x = pairP namexS addressS
        f (P n a) = (n,a)
        g (n,a) = P n a

salaryS ::  EqProof p => Shape p Salary
salaryS = Sdata (App "Salary" []) h (Ep f g)
  where h x = intP
        f (S x) = x
        g x = S x

addressS ::  EqProof p => Shape p Address
addressS = listP charP

namexS ::  EqProof p => Shape p Nam
namexS = listP charP

managerS ::  EqProof p => Shape p Manager
managerS = employeeS

company = Leibniz companyS
dept = Leibniz deptS
subUnit = Leibniz subUnitS
employee = Leibniz employeeS
person = Leibniz personS
salary = Leibniz salaryS
manager = person
namex = string
address = string
string = list(charL)