Zulip Chat Archive

Stream: lean4

Topic: loop invariants


Leonard Wiechmann (Sep 01 2022 at 09:16):

how would i be able to use the fact that for _ in [:n] runs for exactly n iterations?
for example, say i have a struct (i: Nat, h: (P i)), and in each iteration, i derive the next instance of the struct (with i.succ and (P i.succ)).
at the end of the loop, i have (n, (P n)), but how can i get a proof of i = n?
i guess another way of asking that question is, how can i do induction with for loops?

def foo (n: Nat) := Id.run do
  let mut i := 0
  for _ in [:n] do
    i := i + 1
  -- how to derive `Eq i n`?

Mario Carneiro (Sep 01 2022 at 15:31):

you can't, at least not inside the do block itself

Mario Carneiro (Sep 01 2022 at 15:31):

you can prove after the fact that i = n at that point however

Mario Carneiro (Sep 01 2022 at 15:32):

If you want this kind of reasoning inside the program itself you will have to write it in a different way

Leonard Wiechmann (Sep 01 2022 at 18:53):

hmm, i suspected that it might not be possible. break & continue could mess things up.
by "proving after the fact" i assume you mean outside of the system?

Mario Carneiro (Sep 01 2022 at 18:57):

no, I mean in a theorem after the def

Mario Carneiro (Sep 01 2022 at 18:58):

You can reason about do blocks, you just can't make use of that reasoning inside the definition of the do block itself because do blocks are simply typed

Leonard Wiechmann (Sep 02 2022 at 07:48):

oh, that sounds really interesting!
would that work for

def foo (n: Nat) := Id.run do
  let mut i := 0
  for _ in [:n] do
    i := i + 1
  return 4

or would i have to return i?
i don't even know how i'd formulate the theorem's type if i isn't returned. :thinking:

Sebastian Ullrich (Sep 02 2022 at 07:52):

You can only state theorems about the pure value of a term, which i in your code above does not participate in

Mario Carneiro (Sep 02 2022 at 07:55):

your definition is equivalent to def foo (n : Nat) := 4 (and you can prove this). Maybe give a slightly less trivial example where you want a loop invariant?

Mario Carneiro (Sep 02 2022 at 07:56):

if you returned i then the definition would be provably equivalent to def foo (n : Nat) := n so that's one way to express that the loop did in fact run n times

Leonard Wiechmann (Sep 02 2022 at 08:06):

provably equivalent to def foo (n : Nat) := n

ok, that makes sense.
i was messing with an iterative fibonacci function. but i guess that's not possible because "do blocks are simply typed".

Mario Carneiro (Sep 02 2022 at 08:08):

could you write some pseudocode of what you want?

Mario Carneiro (Sep 02 2022 at 08:10):

something like this?

def fib (n : Nat) := Id.run do
  let mut a := 0
  let mut b := 1
  for _ in [:n] do
    (a, b) := (b, a + b)
  return a

Leonard Wiechmann (Sep 02 2022 at 08:10):

something like this.
where termination is proved using acc.

    let mut i: {Nat //  <= n}         := <0, Nat.zero_le n>
    let mut a: {Nat //  = fib i}      := fib i
    let mut b: {Nat //  = fib i.succ} := fib i.succ
    while h: i < n do
        (i, a, b) := (
            <i + 1, Nat.succ_le_of_lt h>
            b,
            a + b)
    let h: (i = n) := Nat.eq_of_le_not_lt i.prop h
    return h.rewrite a

(this would define fib_iter: Pi (n: Nat) -> {Nat // ⋅ = fib n})

Leonard Wiechmann (Sep 02 2022 at 08:15):

though i guess your example with an external proof could be cool too.

Mario Carneiro (Sep 02 2022 at 08:20):

when I want to write a dependently typed program (i.e. the actual execution relies on proofs), I use local tail recursion:

def fib : Nat  Nat
| 0 => 0
| 1 => 1
| n+2 => fib n + fib (n+1)

def fib' (n : Nat) :=
  let rec go
    (i : {i // i  n})
    (a : {a // a = fib i})
    (b : {b // b = fib (i + 1)}) :
    {x // x = fib n} :=
  if h : i < n then
    go i + 1, h b a + b, by rw [a.2, b.2]; rfl
  else
    have : i = n := Nat.le_antisymm i.2 (Nat.ge_of_not_lt h)
    by rw [ this]; exact a
  go 0, Nat.zero_le _ 0, rfl 1, rfl
termination_by go => n - i

Mario Carneiro (Sep 02 2022 at 08:21):

but it is generally preferable to keep the proof separate from the program if it's not necessary for the type correctness of the program (as it is in this case)

Leonard Wiechmann (Sep 02 2022 at 08:25):

i see, that's really cool!
and i definitely see why you'd want the proof separate :D

Leonard Wiechmann (Sep 02 2022 at 08:26):

if i wanted to do an external proof, i'd probably use brec, right? (to get fib i = fib' i for the previous two i)
i'm not too familiar with using lean "practically".

Mario Carneiro (Sep 02 2022 at 08:33):

you usually don't need to use brec directly, since you can write recursive theorems the same way as recursive functions

Leonard Wiechmann (Sep 02 2022 at 08:40):

oh, that makes sense!

Leonard Wiechmann (Sep 02 2022 at 08:40):

def fib3 (n : Nat) := Id.run do
  let mut a := 0
  let mut b := 1
  for _ in [:n] do
    (a, b) := (b, a + b)
  return a


def fib3_eq_fib:  n: Nat, fib3 n = fib n
| 0 => by rfl
| 1 => by rfl
| n+2 => by
  have h: (fib3 n + fib3 (n+1)) = fib3 (n+2) := sorry
  rw [ h]
  rw [(fib3_eq_fib n), (fib3_eq_fib (n+1))]
  rfl

so far i have this, but i don't know how to reason about the fib3 terms.

Mario Carneiro (Sep 02 2022 at 08:54):

The for loop is not optimized for reasoning. You can do it but the terms are pretty bad

Leonard Wiechmann (Sep 02 2022 at 08:59):

i see, so it's theoretically possible but not practical?

Mario Carneiro (Sep 02 2022 at 08:59):

the requisite lemmas don't exist yet

Mario Carneiro (Sep 02 2022 at 09:09):

not that that's ever stopped me

theorem fib3_eq_fib : fib3 n = fib n := by
  let rec go :  fuel i, fuel + i = n 
    Std.Range.forIn.loop (m := Id)
      (fun _ r => ForInStep.yield r.2, r.1 + r.2⟩)
      fuel i n 1 (MProd.mk (fib i) (fib (i+1))) =
    fib n, fib (n+1)⟩
  | 0, i, eq => by simp at eq; simp [Std.Range.forIn.loop, eq]
  | fuel+1, i, eq => by
    rw [Std.Range.forIn.loop]; split <;> rename_i h
    · rw [Nat.le_antisymm (by simp_arith [ eq]) h]; rfl
    · have := go fuel (i+1) (by rw [Nat.add_right_comm] at eq; exact eq)
      rw [fib] at this; simp [this]
  have := go n 0 rfl
  simp [fib] at this; simp [fib3, Id.run, forIn, Std.Range.forIn]; rw [this]

Last updated: Dec 20 2023 at 11:08 UTC