Zulip Chat Archive
Stream: lean4
Topic: Nightly broke well-founded proof
James Gallicchio (Feb 25 2022 at 20:05):
Just updated my toolchain to latest nightly, but going from leanprover/lean4:nightly-2022-02-18
to the next day breaks some of my proofs
https://github.com/JamesGallicchio/LeanColls/blob/main/LeanColls/LazyList.lean#L65
^ this goal was closed before by simp, but now leaves some expression in terms of WellFounded.fix
James Gallicchio (Feb 25 2022 at 20:08):
(And doing the thing I normally do, of tossing a bunch more expressions to simp
, gets stuck on Acc.rec
. I'm sure how any of the WellFounded machinery works in lean 4 :/)
James Gallicchio (Feb 25 2022 at 20:41):
Based on the timing this must be caused by the fix for https://github.com/leanprover/lean4/issues/1017
James Gallicchio (Mar 01 2022 at 16:50):
Just bumping this -- on nightly-2022-03-01
I'm still getting stuck trying to get an expression through Acc.rec
I never had to prove stuff in Lean 3 about functions that use well-founded recursion instead of structural recursion, so I'm not sure if this is something I can fix, or if my old solution should still be working and something broke in nightly
Mario Carneiro (Mar 01 2022 at 23:16):
@James Gallicchio Could you extract a #mwe? This looks like something that needs to be addressed in core (we can probably find a workaround but this is exposing implementation details that don't look deliberate)
James Gallicchio (Mar 01 2022 at 23:39):
This is as small as I can make it:
inductive LazyList (α : Type u)
| nil : LazyList α
| cons (hd : α) (tl : LazyList α) : LazyList α
| delayed (t : Thunk (LazyList α)) : LazyList α
namespace LazyList
variable {α : Type u} {β : Type v} {δ : Type w}
-- Height measures number of nodes (including delayeds)
noncomputable def height (ll : LazyList α) : Nat :=
@LazyList.rec (motive_1 := fun _ => Nat) (motive_2 := fun _ => Nat)
α 0 (fun _ _ ih => ih + 1) (fun _ ih => ih + 1) (fun _ ih => ih ()) ll
@[simp] theorem height_nil : (@LazyList.nil α).height = 0 := rfl
@[simp] theorem height_cons : (@LazyList.cons α a l).height = l.height + 1 := rfl
@[simp] theorem height_delayed (as) :
(@LazyList.delayed α as).height = as.get.height + 1 := by
have : as = ⟨fun x => as.get⟩ := by
cases as with | _ fn => apply congrArg; funext (); rfl
rw [this]; rfl
/-
Length of a list is number of actual elements
in the list, ignoring delays
-/
def length : LazyList α → Nat
| nil => 0
| cons _ as => length as + 1
| delayed as => length as.get
termination_by _ as => as.height
@[simp] theorem length_nil : (@nil α).length = 0 := by simp [length]
@[simp] theorem length_cons : (@cons α a as).length = as.length + 1 := by simp [length]
@[simp] theorem length_delayed (as)
: (@delayed α as).length = as.get.length
:= by
have : as = ⟨fun x => as.get⟩ := by
cases as with | _ fn => apply congrArg; funext (); rfl
rw [this]; rfl
@[simp] def toList : LazyList α → List α
| nil => []
| cons a as => a :: toList as
| delayed as => toList as.get
termination_by _ as => as.height
@[simp] theorem length_toList : (l : LazyList α) → l.toList.length = l.length
:= @rec α
(λ l => l.toList.length = l.length)
(λ t => t.get.toList.length = t.get.length)
(by simp [toList])
(by
intros hd tl tl_ih
simp [tl_ih, toList, Thunk.get]
)
(by
intros t t_ih
assumption
)
(by
intros fn fn_ih
have := fn_ih ()
simp [Thunk.get] at this |-
assumption
)
I could mess around a bit more to try making it smaller but
Leonardo de Moura (Mar 01 2022 at 23:46):
I will take a look at why simp
is exposing the internal encoding. In the meantime, you can avoid the problem by deleting the unnecessary length
theorems, Lean can create them automatically for us.
inductive LazyList (α : Type u)
| nil : LazyList α
| cons (hd : α) (tl : LazyList α) : LazyList α
| delayed (t : Thunk (LazyList α)) : LazyList α
namespace LazyList
variable {α : Type u} {β : Type v} {δ : Type w}
instance : Inhabited (LazyList α) :=
⟨nil⟩
@[inline] protected def pure : α → LazyList α
| a => cons a nil
-- Height measures number of nodes (including delayeds)
noncomputable def height (ll : LazyList α) : Nat :=
@LazyList.rec (motive_1 := fun _ => Nat) (motive_2 := fun _ => Nat)
α 0 (fun _ _ ih => ih + 1) (fun _ ih => ih + 1) (fun _ ih => ih ()) ll
@[simp] theorem height_nil : (@LazyList.nil α).height = 0 := rfl
@[simp] theorem height_cons : (@LazyList.cons α a l).height = l.height + 1 := rfl
@[simp] theorem height_delayed (as) : (@LazyList.delayed α as).height = as.get.height + 1 := by
have : as = ⟨fun x => as.get⟩ := by
cases as with | _ fn => apply congrArg; funext (); rfl
rw [this]; rfl
/-
Length of a list is number of actual elements
in the list, ignoring delays
-/
def length : LazyList α → Nat
| nil => 0
| cons _ as => length as + 1
| delayed as => length as.get
termination_by _ as => as.height
def toList : LazyList α → List α
| nil => []
| cons a as => a :: toList as
| delayed as => toList as.get
termination_by _ as => as.height
attribute [simp] toList length
theorem length_toList (l : LazyList α) : l.toList.length = l.length := by
match l with
| nil => rfl
| cons a as => simp [length_toList as]
| delayed as => simp [length_toList as.get]
termination_by _ as => as.height
def force : LazyList α → Option (α × LazyList α)
| delayed as => force as.get
| nil => none
| cons a as => some (a,as)
termination_by _ as => as.height
theorem toList_force_none (l : LazyList α) : force l = none ↔ l.toList = List.nil := by
match l with
| nil => simp [force]
| delayed as => simp [force, toList_force_none as.get]
| cons a as => simp [force, toList_force_none as]
termination_by _ as => as.height
James Gallicchio (Mar 02 2022 at 01:15):
Oh, huh, so adding the simp attribute after declaration has different behavior than adding it directly to the declaration?
Leonardo de Moura (Mar 02 2022 at 01:52):
I pushed a fix for this issue. I also added a nontrivial SizeOf
instance for types like Thunk
. The heigh
auxiliary function is not needed anymore. We can write the example above as:
inductive LazyList (α : Type u)
| nil : LazyList α
| cons (hd : α) (tl : LazyList α) : LazyList α
| delayed (t : Thunk (LazyList α)) : LazyList α
deriving Inhabited
namespace LazyList
@[inline] protected def pure (a : α) : LazyList α :=
cons a nil
/-
Length of a list is number of actual elements
in the list, ignoring delays
-/
@[simp] def length : LazyList α → Nat
| nil => 0
| cons _ as => length as + 1
| delayed as => length as.get
termination_by _ as => as
@[simp] def toList : LazyList α → List α
| nil => []
| cons a as => a :: toList as
| delayed as => toList as.get
termination_by _ as => as
theorem length_toList (l : LazyList α) : l.toList.length = l.length := by
match l with
| nil => rfl
| cons a as => simp [length_toList as]
| delayed as => simp [length_toList as.get]
termination_by _ as => as
def force : LazyList α → Option (α × LazyList α)
| delayed as => force as.get
| nil => none
| cons a as => some (a,as)
termination_by _ as => as
theorem toList_force_none (l : LazyList α) : force l = none ↔ l.toList = List.nil := by
match l with
| nil => simp [force]
| delayed as => simp [force, toList_force_none as.get]
| cons a as => simp [force, toList_force_none as]
termination_by _ as => as
end LazyList
James Gallicchio (Mar 02 2022 at 02:11):
Ah, amazing! Thank you :)
Leonardo de Moura (Mar 02 2022 at 23:28):
The trivial termination_by _ as => as
annotations in the example above are not needed anymore.
The simplified version is here: https://github.com/leanprover/lean4/blob/master/tests/lean/run/lazylistThunk.lean
Last updated: Dec 20 2023 at 11:08 UTC