module DeBruijnExSubst where

open import Prelude -- using (_∘_)       -- composition, identity
open import Data.Maybe
open import Logic.Identity renaming (subst to subst≡)
import Logic.ChainReasoning
module Chain = Logic.ChainReasoning.Poly.Homogenous _≡_ (\x -> refl) (\x y z -> trans)
open Chain

open import DeBruijn

-- untyped de Bruijn terms
data LamE (A : Set) : Set where
    varE  : A -> LamE A
    appE  : LamE A -> LamE A -> LamE A
    absE  : LamE (Maybe A) -> LamE A
    flatE : LamE (LamE A) -> LamE A

-- functoriality of LamE
lamE : {A B : Set} -> (A -> B) -> LamE A -> LamE B
lamE f (varE a)     = varE (f a)
lamE f (appE t1 t2) = appE (lamE f t1) (lamE f t2)
lamE f (absE r)     = absE (lamE (fmap f) r)
lamE f (flatE r)    = flatE (lamE (lamE f) r)

{- from TCS 05 paper
eval' : {A B : Set} -> (A -> B) -> LamE A -> Lam B
eval' f (varE a)     = var (f a)
eval' f (appE t1 t2) = app (eval' f t1) (eval' f t2)
eval' f (absE r)     = abs (eval' (fmap f) r)
eval' f (flatE r)    = subst id (eval' (eval' f) r)

eval : {A : Set} -> LamE A -> Lam A
eval = eval' id
-}

eval : {A : Set} -> LamE A -> Lam A
eval (varE a)     = var a
eval (appE t1 t2) = app (eval t1) (eval t2)
eval (absE r)     = abs (eval r)
eval (flatE r)    = subst eval (eval r)

evalNAT : {A B : Set}(f : A -> B) -> (t : LamE A) ->
  eval (lamE f t) ≡ lam f (eval t)
evalNAT f (varE a)     = refl
evalNAT f (appE t1 t2) =
  chain> eval (lamE f (appE t1 t2))
     === eval (appE (lamE f t1) (lamE f t2))
       by refl
     === app (eval (lamE f t1)) (eval (lamE f t2))
       by refl
     === app (lam f (eval t1))  (eval (lamE f t2))
       by cong (\ x -> app x (eval (lamE f t2))) (evalNAT f t1)
     === app (lam f (eval t1))  (lam f (eval t2))
       by cong (\ x -> app (lam f (eval t1)) x)  (evalNAT f t2)
     === lam f (app (eval t1) (eval t2))
       by refl
     === lam f (eval (appE t1 t2))
       by refl
evalNAT f (absE r) =
  chain> eval (lamE f (absE r))
     === eval (absE (lamE (fmap f) r))   by  refl
     === abs (eval (lamE (fmap f) r))    by  refl
     === abs (lam (fmap f) (eval r))     by  cong abs (evalNAT (fmap f) r)
     === lam f (abs (eval r))            by  refl
     === lam f (eval (absE r))           by  refl
evalNAT f (flatE r) =
  chain> eval (lamE f (flatE r))
     === eval (flatE (lamE (lamE f) r))
       by refl
     === subst eval (eval (lamE (lamE f) r))
       by refl
     === subst eval (lam (lamE f) (eval r))
       by cong (subst eval) (evalNAT (lamE f) r)
     === subst (eval ∘ lamE f) (eval r)
       by substLaw1 (lamE f) eval (eval r)
     === subst (lam f ∘ eval) (eval r)
       by substExt _ _ (evalNAT f) (eval r)
     === lam f (subst eval (eval r))
       by substLaw2 f eval (eval r)
     === lam f (eval (flatE r))
       by refl

evalNATcor : {A : Set}(ee : LamE (LamE A)) ->
  subst id (eval (lamE eval ee)) ≡ eval (flatE ee)
evalNATcor ee =
  chain> subst id (eval (lamE eval ee))
     === subst id (lam eval (eval ee))  by  cong (subst id) (evalNAT eval ee)
     === subst eval (eval ee)           by  substLaw1 eval id (eval ee)
     === eval (flatE ee)                by  refl
