----------------------------------------------------------------------
-- A very simple theorem prover, using the technique of proof by
-- reflection
--
-- Nils Anders Danielsson
----------------------------------------------------------------------

open import Prelude

module Proof-by-reflection

  -- The module is parametrised by a monoid; a type and some
  -- operations satisfying certain properties.

  {A              : Set}

  (identity       : A)
  (_∙_            : A  A  A)

  (left-identity  :  x  (identity  x)  x)
  (right-identity :  x  (x  identity)  x)
  (assoc          :  x y z  (x  (y  z))  ((x  y)  z))

  -- Some elements of the type, only used in examples.

  (x y            : A)
  where

-- The goal is to automatically prove equality of monoid expressions.

----------------------------------------------------------------------
-- Monoid expressions

-- There is one constructor for every operation, plus one for
-- variables; there may be at most n variables.

data Expr (n : ) : Set where
  var : Fin n  Expr n
  id  : Expr n
  _⊕_ : Expr n  Expr n  Expr n

-- An environment contains one value for every variable.

Env :   Set
Env n = Vec A n

-- The semantics of an expression is a map from an environment to a
-- value.

⟦_⟧ :  {n}  Expr n  Env n  A
 var x    ρ = lookup x ρ
 id       ρ = identity
 e₁  e₂  ρ =  e₁  ρ   e₂  ρ

-- Examples.

e₁ : Expr 2
e₁ = (var zero  id)  var (suc zero)

e₂ : Expr 2
e₂ = id  (var zero  (var (suc zero)  id))

ρ : Vec A 2
ρ = x  y  []

ex₁ =  e₁  ρ

----------------------------------------------------------------------
-- Normal forms

-- A normal form is a list of variables.

Normal :   Set
Normal n = List (Fin n)

-- The semantics of a normal form.

⟦_⟧′ :  {n}  Normal n  Env n  A
 []     ⟧′ ρ = identity
 x  nf ⟧′ ρ = lookup x ρ   nf ⟧′ ρ

-- A normaliser.

normalise :  {n}  Expr n  Normal n
normalise (var x)   = x  []
normalise id        = []
normalise (e₁  e₂) = normalise e₁ ++ normalise e₂

-- The normaliser is homomorphic with respect to _++_/_∙_.

homomorphic :  {n} (nf₁ nf₂ : Normal n) (ρ : Env n) 
               nf₁ ++ nf₂ ⟧′ ρ  ( nf₁ ⟧′ ρ   nf₂ ⟧′ ρ)
homomorphic []        nf₂ ρ = sym (left-identity ( nf₂ ⟧′ ρ))
homomorphic (x  nf₁) nf₂ ρ =
  trans (cong  y  lookup x ρ  y) (homomorphic nf₁ nf₂ ρ))
        (assoc (lookup x ρ) ( nf₁ ⟧′ ρ) ( nf₂ ⟧′ ρ))

-- The normaliser preserves the semantics of the expression.

normalise-correct :
   {n} (e : Expr n) (ρ : Env n)   normalise e ⟧′ ρ   e  ρ
normalise-correct (var x)   ρ = right-identity (lookup x ρ)
normalise-correct id        ρ = refl
normalise-correct (e₁  e₂) ρ =
  trans (homomorphic (normalise e₁) (normalise e₂) ρ)
        (cong₂ _∙_ (normalise-correct e₁ ρ) (normalise-correct e₂ ρ))

-- Thus one can prove that two expressions are equal by proving that
-- their normal forms are equal.

normalisation-theorem :
   {n} (e₁ e₂ : Expr n) (ρ : Env n) 
   normalise e₁ ⟧′ ρ   normalise e₂ ⟧′ ρ 
   e₁  ρ   e₂  ρ
normalisation-theorem e₁ e₂ ρ eq =
  trans (sym (normalise-correct e₁ ρ)) (
  trans eq
        (normalise-correct e₂ ρ))

ex₂ = normalisation-theorem e₁ e₂ ρ refl

----------------------------------------------------------------------
-- The prover

-- We can decide if two normal forms are equal.

infix 5 _≟_

_≟_ :  {n} (nf₁ nf₂ : Normal n)  Maybe (nf₁  nf₂)
[]          []         = just refl
(x₁  nf₁)  (x₂  nf₂) = cong₂ _∷_ <$> x₁ ≟-Fin x₂  nf₁  nf₂
_           _          = nothing

-- Thus we can also decide if two expressions have the same semantics.
--
-- Note that this decision procedure must be /sound/, because it
-- returns a proof. However, we have not proved that it is /complete/.
-- Proving this, or finding a counterexample, is left as an exercise.

prove :  {n} (e₁ e₂ : Expr n)  Maybe (∀ ρ   e₁  ρ   e₂  ρ)
prove e₁ e₂ =
   eq ρ 
     normalisation-theorem e₁ e₂ ρ (cong  nf   nf ⟧′ ρ) eq))
  <$> normalise e₁  normalise e₂

ex₃ = prove e₁ e₂

-- We can also prove things "at compile time".

ex₄ : (x  identity)  y  identity  (x  (y  identity))
ex₄ = fromJust (prove e₁ e₂) ρ

-- The following definition does not, and should not, type check.

-- ex₅ : (x ∙ identity) ∙ y ≡ identity
-- ex₅ = fromJust (prove e₁ id) ρ

-- Another exercise: Extend the algorithm above to other algebraic
-- structures.