inductive Vector (α : Type u) : Nat → Type u
  | nil : Vector α 0
  | cons : α → Vector α n → Vector α (n+1)

infix:67 " :: " => Vector.cons

inductive Ty where
  | int
  | bool
  | fn (a r : Ty)

abbrev Ty.interp : Ty → Type
  | int    => Int
  | bool   => Bool
  | fn a r => a.interp → r.interp

inductive HasType : Fin n → Vector Ty n → Ty → Type where
  | stop : HasType 0 (ty :: ctx) ty
  | pop  : HasType k ctx ty → HasType k.succ (u :: ctx) ty

open HasType (stop pop)

inductive Expr : Vector Ty n → Ty → Type where
  | var   : HasType i ctx ty → Expr ctx ty
  | val   : Int → Expr ctx Ty.int
  | lam   : Expr (a :: ctx) ty → Expr ctx (Ty.fn a ty)
  | app   : Expr ctx (Ty.fn a ty) → Expr ctx a → Expr ctx ty
  | op    : (a.interp → b.interp → c.interp) → Expr ctx a → Expr ctx b → Expr ctx c
  | ife   : Expr ctx Ty.bool → Expr ctx a → Expr ctx a → Expr ctx a
  | delay : (Unit → Expr ctx a) → Expr ctx a

inductive Env : Vector Ty n → Type where
  | nil  : Env Vector.nil
  | cons : Ty.interp a → Env ctx → Env (a :: ctx)

infix:67 " :: " => Env.cons

def Env.lookup : HasType i ctx ty → Env ctx → ty.interp
  | stop,  x :: xs => x
  | pop k, x :: xs => lookup k xs

def Expr.interp (env : Env ctx) : Expr ctx ty → ty.interp
  | var i     => env.lookup i
  | val x     => x
  | lam b     => fun x => b.interp (Env.cons x env)
  | app f a   => f.interp env (a.interp env)
  | op o x y  => o (x.interp env) (y.interp env)
  | ife c t e => if c.interp env then t.interp env else e.interp env
  | delay a   => (a ()).interp env

open Expr

/- Examples -/

def add : Expr ctx (Ty.fn Ty.int (Ty.fn Ty.int Ty.int)) :=
  lam (lam (op (.+.) (var stop) (var (pop stop))))

#eval add.interp Env.nil 10 20

def fact : Expr ctx (Ty.fn Ty.int Ty.int) :=
  lam (ife (op (.==.) (var stop) (val 0))
           (val 1)
           (op (.*.) (delay fun _ => app fact (op (.-.) (var stop) (val 1))) (var stop)))
  decreasing_by sorry

#eval fact.interp Env.nil 10
