/-! # Avigad.Chapter8

Induction and Recursion
-/

namespace Avigad.Chapter8

/-! #### Exercise 1

Open a namespace `Hidden` to avoid naming conflicts, and use the equation
compiler to define addition, multiplication, and exponentiation on the natural
numbers. Then use the equation compiler to derive some of their basic
properties.
-/

namespace ex1

def add : Nat → Nat → Nat
  | m, Nat.zero   => m
  | m, Nat.succ n => Nat.succ (add m n)

def mul : Nat → Nat → Nat
  | _, Nat.zero => 0
  | m, Nat.succ n => add m (mul m n)

def exp : Nat → Nat → Nat
  | _, Nat.zero => 1
  | m, Nat.succ n => mul m (exp m n)

end ex1

/-! #### Exercise 2

Similarly, use the equation compiler to define some basic operations on lists
(like the reverse function) and prove theorems about lists by induction (such as
the fact that `reverse (reverse xs) = xs` for any list `xs`).
-/

namespace ex2

variable {α : Type _}

def reverse : List α → List α
  | [] => []
  | (head :: tail) => reverse tail ++ [head]

-- Proof of `reverse (reverse xs) = xs` shown in previous exercise.

end ex2

/-! #### Exercise 3

Define your own function to carry out course-of-value recursion on the natural
numbers. Similarly, see if you can figure out how to define `WellFounded.fix` on
your own.
-/

namespace ex3

def below {motive : Nat → Sort u} (t : Nat) : Sort (max 1 u) :=
  Nat.recOn t
    (zero := PUnit)
    (succ := fun n ih => PProd (PProd (motive n) ih) (PUnit : Sort (max 1 u)))

/--
A copied implementation of `Nat.brecOn` with the `motive` properly supplied.
Notice the `noncomputable` tag; the code generator does not support the `recOn`
recursor yet, for reasons I haven't fully understood yet. This thread touches on
the topic:

https://leanprover-community.github.io/archive/stream/270676-lean4/topic/code.20generator.20does.20not.20support.20recursor.html
-/
noncomputable def brecOn {motive : Nat → Sort u}
  (t : Nat) (F : (t : Nat) → @below motive t → motive t) : motive t :=
  (Nat.recOn t
    (motive := fun n => PProd
      (motive n)
      (Nat.recOn n PUnit fun n ih => PProd (PProd (motive n) ih) PUnit))
    (zero := { fst := F Nat.zero PUnit.unit, snd := PUnit.unit })
    (succ := fun n ih => {
      fst := F (Nat.succ n) { fst := ih, snd := PUnit.unit },
      snd := { fst := ih, snd := PUnit.unit }
    })).1

#check WellFounded.fix

end ex3

/-! #### Exercise 4

Following the examples in Section Dependent Pattern Matching, define a function
that will append two vectors. This is tricky; you will have to define an
auxiliary function.
-/

namespace ex4

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

namespace Vector

/--
As long we flip the indices in our type signature in the resulting summation,
there is no need for an auxiliary function.
-/
def append : Vector α m → Vector α n → Vector α (n + m)
  | nil, bs => bs
  | cons a as, bs => cons a (append as bs)

end Vector

end ex4

/-! #### Exercise 5

Consider the following type of arithmetic expressions.
-/

namespace ex5

inductive Expr where
  | const : Nat → Expr
  | var : Nat → Expr
  | plus : Expr → Expr → Expr
  | times : Expr → Expr → Expr
  deriving Repr

open Expr

def sampleExpr : Expr :=
  plus (times (var 0) (const 7)) (times (const 2) (var 1))

-- Here `sampleExpr` represents `(v₀ * 7) + (2 * v₁)`. Write a function that
-- evaluates such an expression, evaluating each `var n` to `v n`.

def eval (v : Nat → Nat) : Expr → Nat
  | const n     => n
  | var n       => v n
  | plus e₁ e₂  => eval v e₁ + eval v e₂
  | times e₁ e₂ => eval v e₁ * eval v e₂

def sampleVal : Nat → Nat
  | 0 => 5
  | 1 => 6
  | _ => 0

-- Try it out. You should get 47 here.
#eval eval sampleVal sampleExpr

/-! ##### Constant Fusion

Implement "constant fusion," a procedure that simplifies subterms like `5 + 7
to `12`. Using the auxiliary function `simpConst`, define a function "fuse": to
simplify a plus or a times, first simplify the arguments recursively, and then
apply `simpConst` to try to simplify the result.
-/

def simpConst : Expr → Expr
  | plus (const n₁) (const n₂)  => const (n₁ + n₂)
  | times (const n₁) (const n₂) => const (n₁ * n₂)
  | e                           => e

def fuse : Expr → Expr
  | plus e₁ e₂  => simpConst $ plus (fuse e₁) (fuse e₂)
  | times e₁ e₂ => simpConst $ times (fuse e₁) (fuse e₂)
  | e           => e

theorem simpConst_eq (v : Nat → Nat)
  : ∀ e : Expr, eval v (simpConst e) = eval v e := by
  intro e
  unfold simpConst
  repeat { unfold eval }
  match h : e with
  | const n
  | var n
  | plus  (const   _) (const _)
  | plus  (var     _) _
  | plus  (plus  _ _) _
  | plus  (times _ _) _
  | times (const   _) (const _)
  | times (var     _) _
  | times (plus  _ _) _
  | times (times _ _) _ => rfl
  | plus  _ (var     _)
  | plus  _ (plus  _ _)
  | plus  _ (times _ _)
  | times _ (var     _)
  | times _ (plus  _ _)
  | times _ (times _ _) => simp only
  

theorem fuse_eq (v : Nat → Nat)
  : ∀ e : Expr, eval v (fuse e) = eval v e := by
  intro e
  induction e with
  | const n | var n => unfold fuse; rfl
  | plus e₁ e₂ he₁ he₂ | times e₁ e₂ he₁ he₂ =>
    unfold fuse
    rw [simpConst_eq]
    unfold eval
    rw [he₁, he₂]

-- The last two theorems show that the definitions preserve the value.

end ex5

end Avigad.Chapter8