Zulip Chat Archive

Stream: new members

Topic: Proving something about forIn


Yongyi Chen (Nov 21 2023 at 22:51):

I'm challenging myself to prove that doNothing is the identity function, where

import Mathlib

def doNothing (as : Array α) := Id.run do
  let mut r := #[]
  for a in as do r := r.push a
  return r

What I attempted:

import Mathlib

def doNothing (as : Array α) := Id.run do
  let mut r := #[]
  for a in as do r := r.push a
  return r

theorem pop_concat_last_eq (as : Array α) (h : as.size > 0) : as.pop ++ #[as.data.getLast (List.length_pos.mp h)] = as := by
  apply Array.ext'
  simp
  exact List.dropLast_append_getLast (List.length_pos.mp h)

theorem doNothing_eq_id (as : Array α) : doNothing as = as := by
  induction' h : as.size with n ih generalizing as
  . rw [List.length_eq_zero] at h
    have : as = #[] := by apply Array.ext' h
    subst this
    trivial
  . let bs := as.pop
    let b := as.data.getLast (List.length_pos.mp (Nat.lt_of_sub_eq_succ h))
    have : bs ++ #[b] = as := by exact pop_concat_last_eq as (Nat.lt_of_sub_eq_succ h)
    rw [ this]
    replace ih : doNothing bs = bs := ih bs (by simp only [Array.size_pop, ge_iff_le, Nat.succ_sub_succ_eq_sub, nonpos_iff_eq_zero, tsub_zero, h])
    dsimp [doNothing, Id.run, forIn]
    sorry

I'm hitting a brick wall here. Not sure if this is the right approach.

Kevin Buzzard (Nov 21 2023 at 23:26):

These "how to prove that a completely basic program has no bugs" questions remind me of the Lean chat in summer 2017 when it was mostly "how to prove that a completely basic maths question (such as 2+2<500.678) is true" and there was no norm_num.

Joachim Breitner (Nov 22 2023 at 09:47):

I’d say that doing proofs over monadic code like this will be very painful, without suitable tactics and program logics. I expect there is a way to push this through, but I don’t expect it to be practical.

If you start your proof with unfold doNothing you’ll see that it ’s defined using a function called forIn:

α: Type u_1
as: Array α
 Id.run
    (let r := #[];
    do
    let r 
      forIn as r fun a r =>
          let r := r;
          let r := Array.push r a;
          do
          pure PUnit.unit
          pure (ForInStep.yield r)
    let r : Array α := r
    pure r) =
  as

So I expect you’ll need to define a helper lemma about that forIn, prove that by induction, and then use it outside.

Joachim Breitner (Nov 22 2023 at 09:49):

Practically speaking, if you want to verify basic programs, write them in a pure functional style, e.g. using recursion.

Yongyi Chen (Nov 22 2023 at 14:13):

So it looks like it's not just me, and we don't have (yet) the tools to do this. Thanks!

Tomas Skrivan (Nov 22 2023 at 15:14):

It is definitely possible but not that straightforward as one would hope. Here is version for List. For Array I have to think about it a bit more.

def doNothing (as : List α) := Id.run do
  let mut r := []
  for a in as do r := r.concat a
  return r

@[simp]
theorem forIn_concat_append (as as' : List α)
  : (forIn (m:=Id) as as' fun a' r => ForInStep.yield (List.concat r a')) = as'.append as :=
by
  revert as'
  induction as
  case nil =>
    simp
  case cons a' as h =>
    simp[List.concat,h]
    intro as'
    induction as'
    case nil =>
      simp[List.concat]
    case cons a'' as' h' =>
      simp[h',List.concat,List.append]

example (a : List α)
  : doNothing a = a :=
by
  simp[doNothing,Id.run]

I think having some induction principle for lists or arrays that is peeling off elements from the right would be helpful.

Tomas Skrivan (Nov 22 2023 at 15:45):

I think for arrays it would be helpful to prove this simp theorem analogous to List.forIn_cons

@[simp] theorem Array.forIn_cons [Monad m] (f : α  β  m (ForInStep β)) (a : α) (as : List α) (b : β)
    : forIn (Array.mk (a::as)) b f = f a b >>= fun | ForInStep.done b => pure b | ForInStep.yield b => forIn (Array.mk as) b f := sorry

Tomas Skrivan (Nov 22 2023 at 15:47):

@Yongyi Chen btw it would be helpful if your example code would be self contained and work with the View in Lean 4 playground button in the top right corner of every code block

Yongyi Chen (Nov 22 2023 at 15:47):

Oh, I'll add in the necessary imports!

Tomas Skrivan (Nov 22 2023 at 15:53):

Unfortunately with more complicated code you want to maintain an invariant inside of the for loop. Then it gets hard and there is not much tooling for that right now.

Yongyi Chen (Nov 22 2023 at 16:06):

Yeah, going through the code and showing invariants is how a proof in a typical CS class would go.

Mario Carneiro (Nov 22 2023 at 19:59):

Every time this exercise comes up we find a few more lemmas for Std. I was able to reduce the proof to this:

import Std.Data.Array.Lemmas

--- for std
theorem Array.forIn_eq_data_forIn [Monad m]
    (as : Array α) (b : β) (f : α  β  m (ForInStep β)) :
    forIn as b f = forIn as.data b f := sorry -- proof omitted
@[simp] theorem appendList_nil (arr : Array α) : arr ++ ([] : List α) = arr := Array.ext' (by simp)
@[simp] theorem appendList_cons (arr : Array α) (a : α) (l : List α) :
    arr ++ (a :: l) = arr.push a ++ l := Array.ext' (by simp)

def doNothing (as : Array α) := Id.run do
  let mut r := #[]
  for a in as do r := r.push a
  return r

theorem doNothing_eq_id (as : Array α) : doNothing as = as := by
  simp [doNothing, Array.forIn_eq_data_forIn]
  let f a r : Id (ForInStep (Array α)) := ForInStep.yield (Array.push r a)
  have H l r : forIn l r f = r ++ (l : List α) := by induction l generalizing r <;> simp [*]
  apply Array.ext'; rw [H]; simp [Id.run]

Mario Carneiro (Nov 22 2023 at 20:05):

added in 282e3a1

Mario Carneiro (Nov 22 2023 at 20:15):

Tomas Skrivan said:

Unfortunately with more complicated code you want to maintain an invariant inside of the for loop. Then it gets hard and there is not much tooling for that right now.

There is always an invariant when proving things about loops of any kind, and this one is no exception. It is the inductive hypothesis, and in this case I have written it as saying that forIn l r f = r ++ (l : List α), that is, the effect of the loop is to append as to the initial value r

Jun Yoshida (Nov 23 2023 at 13:53):

I have struggled with such problems several times, too. IMO, it is convenient to relate @forIn m ρ α _ β loop with ρ.foldlM (m:=StateT (ForInStep β) m) (e.g. in the case ρ=List α or Array α) or its combination with a "proof attacher" like List.pmap. If ρ.foldlM has a good induction principle, then we can just use it. I believe that Std.Range also admits such a good foldM.

Jun Yoshida (Nov 23 2023 at 13:56):

From this point of view, it may be worth having some analogue of Haskell's MonoFoldable class.


Last updated: Dec 20 2023 at 11:08 UTC