Zulip Chat Archive
Stream: lean4
Topic: Theorem Proving in lean exercise review request
James Sully (Apr 01 2024 at 13:51):
I went a bit crazy doing a version of the expression evaluator exercise from chapter 7 exercise 3 of Theorem Proving.
Define an inductive data type consisting of terms built up from the following constructors:
- const n, a constant denoting the natural number n
- var n, a variable, numbered n
- plus s t, denoting the sum of s and t
- times s t, denoting the product of s and t
Recursively define a function that evaluates any such term with respect to an assignment of values to the variables.
I've simplified it as much as I know how, does anyone have any suggestions for neatening it?
inductive Expr where
| const : Nat → Expr
| var : Nat → Expr
| plus : Expr → Expr → Expr
| times : Expr → Expr → Expr
namespace Expr
def varInExpr (n : Nat) (e : Expr) : Prop := match e with
| const _ => False
| var m => n = m
| plus s t => varInExpr n s ∨ varInExpr n t
| times s t => varInExpr n s ∨ varInExpr n t
def varInEnv (n : Nat) (env : List (Nat × Nat)) : Prop :=
n ∈ env.map Prod.fst
theorem mem_head_or_tail (x hd : α) (tl : List α) (h : x ∈ hd :: tl)
: x = hd ∨ x ∈ tl := by
cases h with
| head => left; eq_refl
| tail => right; assumption
def getEnv (key : Nat) (env : List (Nat × Nat)) (ok : varInEnv key env) : Nat :=
match env with
| [] => by
exfalso
cases ok
| (key', val) :: rest =>
have h₂ : key = key' ∨ key ∈ rest.map Prod.fst := by
exact mem_head_or_tail key key' (List.map Prod.fst rest) ok
if h₃ : key == key' then
val
else
have ok' : varInEnv key rest := by
rw [show varInEnv key rest = (key ∈ rest.map Prod.fst) from rfl]
simp_all
getEnv key rest ok'
example : varInExpr 3 (var 3) := rfl
example : varInExpr 3 (plus (var 3) (const 10)) := by left; rfl
def eval (env : List (Nat × Nat))
(expr : Expr)
(_ : ∀ n, varInExpr n expr → varInEnv n env)
: Nat :=
match h₂ : expr with
| const n => n
| var i =>
have : varInExpr i expr := by rw [h₂]; rfl
have : varInEnv i env := by simp_all only
getEnv i env ‹varInEnv i env›
| plus s t =>
have sOk : ∀ n, varInExpr n s → varInEnv n env := by
intro n h₃
have : varInExpr n (s.plus t) := by left; exact h₃
simp_all only
have tOk : ∀ n, varInExpr n t → varInEnv n env := by
intro n h₃
have : varInExpr n (s.plus t) := by right; exact h₃
simp_all only
eval env s sOk + eval env t tOk
| times s t =>
have sOk : ∀ n, varInExpr n s → varInEnv n env := by
intro n h₃
have : varInExpr n (s.times t) := by left; exact h₃
simp_all only
have tOk : ∀ n, varInExpr n t → varInEnv n env := by
intro n h₃
have : varInExpr n (s.times t) := by right; exact h₃
simp_all only
eval env s sOk * eval env t tOk
#eval
let env := [(1, 42)]
let expr := var 1
let ok : ∀ n, varInExpr n expr → varInEnv n env := by
rw [show expr = var 1 from rfl]
intro n h
rw [show varInExpr n (var 1) = (n = 1) by rfl] at h
suffices h₁ : varInEnv n env = (1 ∈ [1]) by rw [h₁]; left
calc varInEnv n env
_ = (n ∈ env.map Prod.fst) := rfl
_ = (n ∈ [1]) := rfl
_ = (1 ∈ [1]) := by rw [h]
eval env expr ok
James Sully (Apr 01 2024 at 13:53):
in particular I'm not happy with the repetition in the plus
and times
branches of eval
James Sully (Apr 01 2024 at 13:57):
alternative formulations of varInExpr
, varInEnv
, or the precondition for eval
that make the proofs easier would be welcome as well
James Sully (Apr 01 2024 at 14:03):
This would obviously be a lot simpler if the env was a Nat -> Nat
, or eval
returned Option Nat
or something, but this is more fun
Marcus Rossel (Apr 01 2024 at 16:31):
I'm guessing not all of this is relevant to you, but if I wanted to go super fancy I'd do:
import Lean
inductive Expr where
| const : Nat → Expr
| var : Nat → Expr
| plus : Expr → Expr → Expr
| times : Expr → Expr → Expr
namespace Expr
instance : OfNat Expr n where
ofNat := .const n
prefix:100 "%" => Expr.var
instance : Add Expr where
add := .plus
instance : Mul Expr where
mul := .times
def Mem (n : Nat) : Expr → Prop
| const _ => False
| %m => n = m
| s + t | .times s t => Mem n s ∨ Mem n t -- We can't use `*` for pattern matching of `.times`: https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Mul.20match_pattern
instance : Membership Nat Expr where
mem := Mem
abbrev Env := List (Nat × Nat)
namespace Env
def Mem (n : Nat) (env : Env) : Prop :=
n ∈ env.map Prod.fst
instance : Membership Nat Env where
mem := Env.Mem
theorem Mem.iff_mem (env : Env) : (n ∈ env) ↔ (n ∈ env.map Prod.fst) :=
Iff.rfl
local macro "get_mem" : tactic => `(tactic| simp_all [Mem.iff_mem])
def get (key : Nat) (env : Env) (_ : key ∈ env := by get_mem) : Nat :=
let (key', val) :: rest := env
if _ : key == key' then val else get key rest
def Completes (env : Env) (expr : Expr) : Prop :=
∀ n, n ∈ expr → n ∈ env
infixr:50 " ⊢ " => Completes
namespace Completes
theorem var (h : env ⊢ %n) : n ∈ env :=
h _ rfl
theorem plus_left (h : env ⊢ s + t) : env ⊢ s :=
fun _ hn => h _ (.inl hn)
theorem plus_right (h : env ⊢ s + t) : env ⊢ t :=
fun _ hn => h _ (.inr hn)
theorem times_left (h : env ⊢ s * t) : env ⊢ s :=
fun _ hn => h _ (.inl hn)
theorem times_right (h : env ⊢ s * t) : env ⊢ t :=
fun _ hn => h _ (.inr hn)
end Completes
end Env
example : 3 ∈ %3 := rfl
example : 3 ∈ %3 + 10 := by left; rfl
local macro "eval_compl" : tactic => `(tactic|
first
| simp_all [Env.Completes.plus_left ‹_›]
| simp_all [Env.Completes.plus_right ‹_›]
| simp_all [Env.Completes.times_left ‹_›]
| simp_all [Env.Completes.times_right ‹_›]
)
def eval (env : Env) (expr : Expr) (compl : env ⊢ expr := by eval_compl) : Nat :=
match expr with
| const n => n
| var i => env.get i compl.var
| plus s t => eval env s + eval env t
| times s t => eval env s * eval env t
#eval
let env := [(1, 42)]
let expr := %1
eval env expr <| by intro; simp_all [expr, env, (· ∈ ·), Expr.Mem, Env.Mem, List.Mem.head]
James Sully (Apr 01 2024 at 21:01):
Some good stuff in here, thanks. I especially like iff_mem
and get_mem
for reducing the definitional rewrites. In the general case I'd probably be worried about a Membership Nat Expr
instance for vars bc I might theoretically also want to talk about whether particular consts are in the expression. At that point maybe it'd be worth creating types for Var
and Const
and having
inductive Expr where
| const : Const → Expr
| var : Var → Expr
James Sully (Apr 01 2024 at 21:04):
I don't know enough maths to know why ⊢
is an appropriate choice of symbol for this, but it's cool
Henrik Böving (Apr 01 2024 at 21:09):
Turnstyle has a lot of meanings in the logic world, generally there is always some context on the left ("context" is generally a name for "collection of variables, often with associated types") and then some expression on the right that is considered "valid" in that context. "valid" can mean a lot of things, in this case "all variables are bound" but for example the notation is also common as
\Gamma \turnstyle x : \tau
in type theory where it means "the expression x has type tau in context Gamma
James Sully (Apr 01 2024 at 21:09):
default argument with a tactic is a cool strategy. I think indexing works that way with get_elem_tactic
James Sully (Apr 01 2024 at 21:10):
Henrik Böving said:
Turnstyle has a lot of meanings in the logic world, generally there is always some context on the left ("context" is generally a name for "collection of variables, often with associated types") and then some expression on the right that is considered "valid" in that context. "valid" can mean a lot of things, in this case "all variables are bound" but for example the notation is also common as
\Gamma \turnstyle x : \tau
in type theory where it means "the expression x has type tau in context Gamma
That makes sense, the infoview would be an example
Henrik Böving (Apr 01 2024 at 21:13):
Precisely! The infoview is the dump of the context + expected type at that point and you are constructing the x
James Sully (Apr 01 2024 at 21:28):
Marcus Rossel said:
def get (key : Nat) (env : Env) (_ : key ∈ env := by get_mem) : Nat := let (key', val) :: rest := env if _ : key == key' then val else get key rest
What specifically is the criteria for lean being able to know that a case is inconsistent and can be left out, like the []
case here?
Marcus Rossel (Apr 02 2024 at 09:42):
@James Sully I'm not sure. But in this case the []
branch could be closed using by contradiction
, so perhaps that's one of the things Lean checks.
James Sully (Apr 02 2024 at 10:41):
I did a quick experiment:
#eval Id.run do
let myList₁ : List Nat := []
-- match myList₁ with
-- | [] => 1
-- | n :: ns => by contradiction -- "error: tactic 'contradiction' failed"
match h : myList₁ with
| [] => 1
| n :: ns => by contradiction -- ok, since we have `h`
-- let [] := myList₁ -- "error: missing cases: (List.cons _ _)"
have : myList₁ = [] := rfl
let [] := myList₁ -- ok
It's a bit strange to me that the let
before the have
is an error, it feels like something the system ought to be able to work out.
Could lean not whenever it sees
let x := e
automatically also add an anonymous hypothesis that x = e
to the context? I'm sure there's some good reason this isn't done, but I don't immediately see what it is.
Kevin Buzzard (Apr 02 2024 at 10:45):
let [] := ...
surely makes no sense, because you are in no position to say what you want []
to mean; it already means something.
James Sully (Apr 02 2024 at 10:47):
It's just a pattern match, I can easily switch the whole thing around to use cons instead, it makes no difference. But there would be destructuring bindings, so the statement would have a purpose, if that bothers you.
James Sully (Apr 02 2024 at 10:54):
here it is the other way around, if that's less distracting
def main : IO Unit := do
let myList₁ : List Nat := [1]
-- match myList₁ with
-- | [] => by contradiction -- error: tactic 'contradiction' failed
-- | n :: ns => 1
let _ := match h : myList₁ with
| [] => by contradiction -- ok, since we have `h`
| n :: ns => 1
-- let [n] := myList₁ -- error: missing cases: (List.cons _ (List.cons _ _))
have : myList₁ = [1] := rfl
let [n] := myList₁ -- ok
IO.println n -- prints 1
Last updated: May 02 2025 at 03:31 UTC