Zulip Chat Archive

Stream: lean4

Topic: Structural recursion hint fails


Wojciech Nawrocki (Oct 27 2023 at 00:05):

Hello WfRel experts! I gave Rustan's challenge a shot using a big-step normalization relation. But proofs by structural recursion on derivations of this relation are failing, and I am not entirely sure how to fix them other than by coming up with a manual termination measure based on a data-relevant argument . I would be grateful for tips as to why thesizeOf h < sizeOf h' hint doesn't help.

import Mathlib.Data.Set.Finite
import Mathlib.Tactic.Linarith

abbrev Var := String

inductive Expr where
  | lit : Bool  Expr
  | var : Var  Expr
  | ite : Expr  Expr  Expr  Expr
  deriving DecidableEq, Inhabited

abbrev Env := List (Var × Bool)

namespace Env

instance : Membership (Var × Bool) Env :=
  fun (x, b) Γ => (x, b)  Γ

def vars : Env  Finset Var :=
  List.foldr (fun (x, _) acc => insert x acc) 

@[simp] theorem vars_cons : vars (p :: Γ) = insert p.1 (vars Γ) := by
  simp [vars]

def getVar (Γ : Env) (x : Var) : Option Bool :=
  Γ.find? (·.1 = x) |>.map (·.snd)

end Env

namespace Expr

def tr : Expr := lit true
def fls : Expr := lit false

@[simp] def eval (Γ : Env) : Expr  Option Bool
  | lit b => b
  | var s => Γ.getVar s
  | ite c t f => do
    let cv  eval Γ c
    if cv then eval Γ t
    else eval Γ f

@[simp] def fv : Expr  Finset Var
  | lit _ => 
  | var x => {x}
  | ite c t f => fv c  fv t  fv f

/-- A big-step 'normalizes to' relation `Γ | e ↝ e'`.
The output is guaranteed to be in normal form.
We first verify the correctness of this relation,
and then write a function to compute it. -/
inductive Norm : Env  Expr  Expr  Prop
  | nLit : Norm Γ (lit b) (lit b)
  | nVar : x  Γ.vars  Norm Γ (var x) (var x)
  | nVarMem : (x, b)  Γ  Norm Γ (var x) (lit b)
  | nIteTr : Norm Γ c tr  Norm Γ t t'  Norm Γ (ite c t f) t'
  | nIteFls : Norm Γ c fls  Norm Γ f f'  Norm Γ (ite c t f) f'
  | nIteVarEq :
    Norm Γ c (var x)  Norm ((x, true)::Γ) t t'  Norm ((x, false)::Γ) f f'  t' = f' 
    Norm Γ (ite c t f) t'
  | nIteVarNe :
    Norm Γ c (var x)  Norm ((x, true)::Γ) t t'  Norm ((x, false)::Γ) f f'  t'  f' 
    Norm Γ (ite c t f) (ite (var x) t' f')
  | nIteIteEq :
    Norm Γ c (ite (var x) t' f')  Norm ((x, true)::Γ) (ite t' t f) t''  Norm ((x, false)::Γ) (ite f' t f) f''  t'' = f'' 
    Norm Γ (ite c t f) t''
  | nIteIteNe :
    Norm Γ c (ite (var x) t' f')  Norm ((x, true)::Γ) (ite t' t f) t''  Norm ((x, false)::Γ) (ite f' t f) f''  t''  f'' 
    Norm Γ (ite c t f) (ite (var x) t'' f'')

open Norm
local notation Γ " ∣ " e " ↝ " e':30 => Norm Γ e e'

/-- `Nf X e` means that `e` is in normal form and none of its free variables appear in `X`. -/
inductive Nf : Finset Var  Expr  Prop
  | nfLit : Nf X (lit b)
  | nfVar : x  X  Nf X (var x)
  | nfIte : x  X  t  f  Nf (insert x X) t  Nf (insert x X) f  Nf X (ite (var x) t f)
open Nf

theorem Nf.var_subset (hYX : Y  X) : Nf X e  Nf Y e
  | nfLit => nfLit
  | nfVar hX => nfVar fun hY => hX (hYX hY)
  | nfIte hX hNe h₁ h₂ =>
    nfIte (fun hY => hX (hYX hY)) hNe
      (h₁.var_subset (Y.insert_subset_insert _ hYX))
      (h₂.var_subset (Y.insert_subset_insert _ hYX))

theorem finsetext {A B : Finset α} (x : α) : A = B  (x  A  x  B) :=
  fun h => iff_of_eq (congrArg (Membership.mem x) h)

theorem Nf.disjoint_fv : Nf X e  X  fv e =  := by
  intro h; induction h with
  | nfLit | nfVar => simp [*]
  | nfIte _ _ _ _ ih₁ ih₂ =>
    apply Finset.subset_empty.mp
    intro x h
    have ih₁ := finsetext x ih₁
    have ih₂ := finsetext x ih₂
    simp at h ih₁ ih₂ 
    aesop

theorem Norm.nf : Γ  e  e'  Nf Γ.vars e'
  | nLit | nVarMem _ => nfLit
  | nVar h => nfVar h
  | h'@(nIteTr _ h) | h'@(nIteFls _ h) =>
    have : sizeOf h < sizeOf h' := sorry -- why doesn't this help?
    h.nf
  | nIteVarEq _ he hf h => he.nf.var_subset (by simp [Finset.subset_insert])
  | nIteVarNe hc ht hf h => by
    have hc := hc.nf.disjoint_fv
    have ht := ht.nf
    have hf := hf.nf
    apply nfIte <;> aesop
  | nIteIteEq _ he hf h => he.nf.var_subset (by simp [Finset.subset_insert])
  | nIteIteNe (x := x) hc ht hf h => by
    have hc := finsetext x hc.nf.disjoint_fv
    have ht := ht.nf
    have hf := hf.nf
    apply nfIte <;> aesop
  termination_by nf _ _ _ h => sizeOf h

Mario Carneiro (Oct 27 2023 at 03:31):

@Wojciech Nawrocki Have you tried proving that have statement? Because it's not true, and the reason simp fails is because the preprocessing reduced the goal to False.

    have : sizeOf h = sizeOf h' := rfl

Mario Carneiro (Oct 27 2023 at 03:32):

It doesn't work because Γ ∣ e ↝ e', being a proposition, has at most one element and hence its SizeOf instance is trivial

Mario Carneiro (Oct 27 2023 at 03:33):

The match compiler doesn't understand structural recursion over inductive propositions, but you should be able to write this using the induction tactic instead

Mario Carneiro (Oct 27 2023 at 03:38):

theorem Norm.nf : Γ  e  e'  Nf Γ.vars e' := by
  intro h; induction h with
  | nLit | nVarMem _ => exact nfLit
  | nVar h => exact nfVar h
  | nIteTr _ h _ ih | nIteFls _ h _ ih => exact ih
  | nIteVarEq _ _ _ _ _ ihf => exact ihf.var_subset (by simp [Finset.subset_insert])
  | nIteVarNe _ _ _ h ihc iht ihf =>
    have hc := ihc.disjoint_fv
    apply nfIte <;> aesop
  | nIteIteEq _ _ _ _ _ ihf => exact ihf.var_subset (by simp [Finset.subset_insert])
  | @nIteIteNe _ _ x _ _ _ _ _ _ _ _ _ h ihc iht ihf =>
    have hc := finsetext x ihc.disjoint_fv
    apply nfIte <;> aesop

Wojciech Nawrocki (Oct 27 2023 at 03:56):

Ah, false assumption would do it! Of course we can't eliminate Prop into Nat. induction was the first thing I tried, but it gave me a scary error about indices. It works now, however. Thanks!


Last updated: Dec 20 2023 at 11:08 UTC