-- An evaluator for well-typed lambda terms.

module LambdaEvaluator where

open import Bool
open import List

infixr 8 _=>_
data Type : Set where
  bool : Type -- a base type
  _=>_ : Type → Type → Type

-- A context is a plain list of types. We don't care about the length.
Context = List Type

-- We can define a pattern synonym to recover the right-to-left notation
-- for contexts.
infixl 1 _,_
pattern _,_ xs x = x :: xs

-- deBruijn indices are more interesting now!
-- Proof reading: A proof of x ∈ xs is simply
-- an index into xs where x can be found.
data _∈_ {A : Set} (x : A) : List A → Set where
  zero : ∀ {xs} → x ∈ (x :: xs)
  succ : ∀ {xs y} → x ∈ xs → x ∈ (y :: xs)

-- Typed lambda terms!
data Term (Γ : Context) : Type → Set where
  var : ∀ {a} → a ∈ Γ → Term Γ a
  lam : ∀ {a b} → Term (Γ , a) b → Term Γ (a => b)
  app : ∀ {a b} → Term Γ (a => b) → Term Γ a → Term Γ b

-- Evaluation ---------

-- We use Agda as the value domain.
Value : Type → Set
Value bool     = Bool
Value (a => b) = Value a → Value b

-- More interesting lists, where each element can have a different type.
-- Proof reading: A proof of All P xs is a list of proofs P x for each
-- element x in xs.
data All {A : Set} (P : A → Set) : List A → Set where
  []   : All P []
  _::_ : ∀ {x xs} → P x → All P xs → All P (x :: xs)

-- If we know All P xs and x ∈ xs, it follows that P x. Also known as
-- looking up the nth element of a list.
lookupAll : ∀ {A} {P : A → Set} {x xs} → All P xs → x ∈ xs → P x
lookupAll (px :: ps) zero     = px
lookupAll (px :: ps) (succ i) = lookupAll ps i

-- An environment is a list of values, one for each element
-- of the context.
Env : Context → Set
Env Γ = All Value Γ

-- Evaluation practically writes itself.
eval : ∀ {Γ a} → Env Γ → Term Γ a → Value a
eval env (var x)   = lookupAll env x
eval env (lam v)   = λ x → eval (x :: env) v
eval env (app u v) = eval env u (eval env v)

-- Examples -------

-- First some convenient notation.
infixl 9 _∙_
pattern _∙_ u v = app u v

pattern varâ‚€ = var zero
pattern var₁ = var (succ zero)
pattern varâ‚‚ = var (succ (succ zero))

Γ₁ : Context
Γ₁ = bool :: bool => bool :: bool :: []

v₀ : bool ∈ Γ₁
vâ‚€ = zero

v₁ : (bool => bool) ∈ Γ₁
v₁ = succ zero

v₂ : bool ∈ Γ₁
vâ‚‚ = succ (succ zero)

tm₁ : Term Γ₁ (bool => bool)
tm₁ = var₁

tm₂ : Term Γ₁ (bool => bool)
tm₂ = lam (var₂ ∙ var₀)

run₂ : Bool → Bool
runâ‚‚ = eval (false :: not :: true :: []) tmâ‚‚