Skip to content

Commit 0a43c13

Browse files
authored
feat: lemmas about iterator collectors (#8380)
This PR provides simple lemmas about `toArray`, `toList` and `toListRev` for the iterator library. It also changes the definition of `Iter` and `IterM` so that they aren't equal anymore and in particular not definitionally equal. While it was very convenient to have them be definitionally equal when working with dependent code, it was also confusing and annoying that one would sometimes end up with something like `it.toList = IterM.toList it`, where `it : Iter β`.
1 parent 1138062 commit 0a43c13

File tree

15 files changed

+530
-28
lines changed

15 files changed

+530
-28
lines changed

src/Std/Data/Iterators.lean

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Std.Data.Iterators.Basic
88
import Std.Data.Iterators.Producers
99
import Std.Data.Iterators.Consumers
1010
import Std.Data.Iterators.Internal
11+
import Std.Data.Iterators.Lemmas
1112

1213
/-!
1314
# Iterators
@@ -92,7 +93,7 @@ All of the following module names are prefixed with `Std.Data.Iterators`.
9293
9394
### Verification API
9495
95-
`Lemmas` will provide the means to verify programs that use iterators.
96+
`Lemmas` provides the means to verify programs that use iterators.
9697
9798
### Implementation details
9899

src/Std/Data/Iterators/Basic.lean

Lines changed: 95 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,6 @@ namespace Std
1919

2020
namespace Iterators
2121

22-
/--
23-
`BaseIter` is the common data structure underlying `Iter` and `IterM`. API users should never
24-
use `BaseIter` directly, only `Iter` and `IterM`.
25-
-/
26-
structure BaseIter {α : Type w} (m : Type w → Type w') (β : Type w) : Type w where
27-
/--
28-
Internal implementation detail of the iterator.
29-
-/
30-
internalState : α
31-
3222
/--
3323
An iterator that sequentially emits values of type `β` in the monad `m`. It may be finite
3424
or infinite.
@@ -68,7 +58,9 @@ def x := [1, 2, 3].iterM IO
6858
def x := ([1, 2, 3].iterM IO : IterM IO Nat)
6959
```
7060
-/
71-
def IterM {α : Type w} (m : Type w → Type w') (β : Type w) := BaseIter (α := α) m β
61+
structure IterM {α : Type w} (m : Type w → Type w') (β : Type w) where
62+
/-- Internal implementation detail of the iterator. -/
63+
internalState : α
7264

7365
/--
7466
An iterator that sequentially emits values of type `β`. It may be finite
@@ -109,29 +101,41 @@ def x := [1, 2, 3].iter
109101
def x := ([1, 2, 3].iter : Iter Nat)
110102
```
111103
-/
112-
def Iter {α : Type w} (β : Type w) := BaseIter (α := α) Id β
104+
structure Iter {α : Type w} (β : Type w) where
105+
/-- Internal implementation detail of the iterator. -/
106+
internalState : α
113107

114108
/--
115109
Converts a pure iterator (`Iter β`) into a monadic iterator (`IterM Id β`) in the
116110
identity monad `Id`.
117111
-/
118112
def Iter.toIterM {α : Type w} {β : Type w} (it : Iter (α := α) β) : IterM (α := α) Id β :=
119-
it
113+
⟨it.internalState⟩
120114

121115
/--
122116
Converts a monadic iterator (`IterM Id β`) over `Id` into a pure iterator (`Iter β`).
123117
-/
124-
def IterM.toPureIter {α : Type w} {β : Type w} (it : IterM (α := α) Id β) : Iter (α := α) β :=
125-
it
118+
def IterM.toIter {α : Type w} {β : Type w} (it : IterM (α := α) Id β) : Iter (α := α) β :=
119+
⟨it.internalState⟩
120+
121+
@[simp]
122+
theorem Iter.toIter_toIterM {α : Type w} {β : Type w} (it : Iter (α := α) β) :
123+
it.toIterM.toIter = it :=
124+
rfl
126125

127126
@[simp]
128-
theorem Iter.toPureIter_toIterM {α : Type w} {β : Type w} (it : Iter (α := α) β) :
129-
it.toIterM.toPureIter = it :=
127+
theorem Iter.toIter_comp_toIterM {α : Type w} {β : Type w} :
128+
IterM.toIter ∘ Iter.toIterM (α := α) (β := β) = id :=
130129
rfl
131130

132131
@[simp]
133-
theorem Iter.toIterM_toPureIter {α : Type w} {β : Type w} (it : IterM (α := α) Id β) :
134-
it.toPureIter.toIterM = it :=
132+
theorem Iter.toIterM_toIter {α : Type w} {β : Type w} (it : IterM (α := α) Id β) :
133+
it.toIter.toIterM = it :=
134+
rfl
135+
136+
@[simp]
137+
theorem Iter.toIterM_comp_toIter {α : Type w} {β : Type w} :
138+
Iter.toIterM ∘ IterM.toIter (α := α) (β := β) = id :=
135139
rfl
136140

137141
section IterStep
@@ -169,6 +173,27 @@ def IterStep.successor : IterStep α β → Option α
169173
| .skip it => some it
170174
| .done => none
171175

176+
/--
177+
If present, applies `f` to the iterator of an `IterStep` and replaces the iterator
178+
with the result of the application of `f`.
179+
-/
180+
@[always_inline, inline]
181+
def IterStep.mapIterator {α' : Type u'} (f : α → α') : IterStep α β → IterStep α' β
182+
| .yield it out => .yield (f it) out
183+
| .skip it => .skip (f it)
184+
| .done => .done
185+
186+
@[simp]
187+
theorem IterStep.mapIterator_mapIterator {α' : Type u'} {α'' : Type u''}
188+
{f : α → α'} {g : α' → α''} {step : IterStep α β} :
189+
(step.mapIterator f).mapIterator g = step.mapIterator (g ∘ f) := by
190+
cases step <;> rfl
191+
192+
@[simp]
193+
theorem IterStep.mapIterator_id {step : IterStep α β} :
194+
step.mapIterator id = step := by
195+
cases step <;> rfl
196+
172197
/--
173198
A variant of `IterStep` that bundles the step together with a proof that it is "plausible".
174199
The plausibility predicate will later be chosen to assert that a state is a plausible successor
@@ -203,6 +228,20 @@ def PlausibleIterStep.done {IsPlausibleStep : IterStep α β → Prop}
203228
(h : IsPlausibleStep .done) : PlausibleIterStep IsPlausibleStep :=
204229
⟨.done, h⟩
205230

231+
/--
232+
A more convenient `cases` eliminator for `PlausibleIterStep`.
233+
-/
234+
@[elab_as_elim, cases_eliminator]
235+
abbrev PlausibleIterStep.casesOn {IsPlausibleStep : IterStep α β → Prop}
236+
{motive : PlausibleIterStep IsPlausibleStep → Sort x} (s : PlausibleIterStep IsPlausibleStep)
237+
(yield : ∀ it' out h, motive ⟨.yield it' out, h⟩)
238+
(skip : ∀ it' h, motive ⟨.skip it', h⟩)
239+
(done : ∀ h, motive ⟨.done, h⟩) : motive s :=
240+
match s with
241+
| .yield it' out h => yield it' out h
242+
| .skip it' h => skip it' h
243+
| .done h => done h
244+
206245
end IterStep
207246

208247
/--
@@ -296,7 +335,7 @@ is up to the `Iterator` instance but it should be strong enough to allow termina
296335
-/
297336
def Iter.IsPlausibleStep {α : Type w} {β : Type w} [Iterator α Id β]
298337
(it : Iter (α := α) β) (step : IterStep (Iter (α := α) β) β) : Prop :=
299-
it.toIterM.IsPlausibleStep step
338+
it.toIterM.IsPlausibleStep (step.mapIterator Iter.toIterM)
300339

301340
/--
302341
The type of the step object returned by `Iter.step`, containing an `IterStep`
@@ -305,6 +344,37 @@ and a proof that this is a plausible step for the given iterator.
305344
def Iter.Step {α : Type w} {β : Type w} [Iterator α Id β] (it : Iter (α := α) β) :=
306345
PlausibleIterStep (Iter.IsPlausibleStep it)
307346

347+
/--
348+
Converts an `Iter.Step` into an `IterM.Step`.
349+
-/
350+
@[always_inline, inline]
351+
def Iter.Step.toMonadic {α : Type w} {β : Type w} [Iterator α Id β] {it : Iter (α := α) β}
352+
(step : it.Step) : it.toIterM.Step :=
353+
⟨step.val.mapIterator Iter.toIterM, step.property⟩
354+
355+
/--
356+
Converts an `IterM.Step` into an `Iter.Step`.
357+
-/
358+
@[always_inline, inline]
359+
def IterM.Step.toPure {α : Type w} {β : Type w} [Iterator α Id β] {it : IterM (α := α) Id β}
360+
(step : it.Step) : it.toIter.Step :=
361+
⟨step.val.mapIterator IterM.toIter, (by simp [Iter.IsPlausibleStep, step.property])⟩
362+
363+
@[simp]
364+
theorem IterM.Step.toPure_yield {α β : Type w} [Iterator α Id β] {it : IterM (α := α) Id β}
365+
{it' out h} : IterM.Step.toPure (⟨.yield it' out, h⟩ : it.Step) = .yield it'.toIter out h :=
366+
rfl
367+
368+
@[simp]
369+
theorem IterM.Step.toPure_skip {α β : Type w} [Iterator α Id β] {it : IterM (α := α) Id β}
370+
{it' h} : IterM.Step.toPure (⟨.skip it', h⟩ : it.Step) = .skip it'.toIter h :=
371+
rfl
372+
373+
@[simp]
374+
theorem IterM.Step.toPure_done {α β : Type w} [Iterator α Id β] {it : IterM (α := α) Id β}
375+
{h} : IterM.Step.toPure (⟨.done, h⟩ : it.Step) = .done h :=
376+
rfl
377+
308378
/--
309379
Asserts that a certain output value could plausibly be emitted by the given iterator in its next
310380
step.
@@ -319,15 +389,15 @@ given iterator `it`.
319389
-/
320390
def Iter.IsPlausibleSuccessorOf {α : Type w} {β : Type w} [Iterator α Id β]
321391
(it' it : Iter (α := α) β) : Prop :=
322-
it'.toIterM.IsPlausibleSuccessorOf it
392+
it'.toIterM.IsPlausibleSuccessorOf it.toIterM
323393

324394
/--
325395
Asserts that a certain iterator `it'` could plausibly be the directly succeeding iterator of another
326396
given iterator `it` while no value is emitted (see `IterStep.skip`).
327397
-/
328398
def Iter.IsPlausibleSkipSuccessorOf {α : Type w} {β : Type w} [Iterator α Id β]
329399
(it' it : Iter (α := α) β) : Prop :=
330-
it'.toIterM.IsPlausibleSkipSuccessorOf it
400+
it'.toIterM.IsPlausibleSkipSuccessorOf it.toIterM
331401

332402
/--
333403
Makes a single step with the given iterator `it`, potentially emitting a value and providing a
@@ -336,7 +406,7 @@ the termination measures `it.finitelyManySteps` and `it.finitelyManySkips`.
336406
-/
337407
@[always_inline, inline]
338408
def Iter.step {α β : Type w} [Iterator α Id β] (it : Iter (α := α) β) : it.Step :=
339-
it.toIterM.step
409+
it.toIterM.step.run.toPure
340410

341411
end Pure
342412

@@ -415,14 +485,14 @@ with `IterM.finitelyManySteps`.
415485
theorem Iter.TerminationMeasures.Finite.rel_of_yield
416486
{α : Type w} {β : Type w} [Iterator α Id β]
417487
{it it' : Iter (α := α) β} {out : β} (h : it.IsPlausibleStep (.yield it' out)) :
418-
IterM.TerminationMeasures.Finite.Rel ⟨it'⟩ ⟨it⟩ :=
488+
IterM.TerminationMeasures.Finite.Rel ⟨it'.toIterM⟩ ⟨it.toIterM⟩ :=
419489
IterM.TerminationMeasures.Finite.rel_of_yield h
420490

421491
@[inherit_doc Iter.TerminationMeasures.Finite.rel_of_yield]
422492
theorem Iter.TerminationMeasures.Finite.rel_of_skip
423493
{α : Type w} {β : Type w} [Iterator α Id β]
424494
{it it' : Iter (α := α) β} (h : it.IsPlausibleStep (.skip it')) :
425-
IterM.TerminationMeasures.Finite.Rel ⟨it'⟩ ⟨it⟩ :=
495+
IterM.TerminationMeasures.Finite.Rel ⟨it'.toIterM⟩ ⟨it.toIterM⟩ :=
426496
IterM.TerminationMeasures.Finite.rel_of_skip h
427497

428498
macro_rules | `(tactic| decreasing_trivial) => `(tactic|

src/Std/Data/Iterators/Consumers.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Authors: Paul Reichert
55
-/
66
prelude
77
import Std.Data.Iterators.Consumers.Monadic
8+
import Std.Data.Iterators.Consumers.Access
89
import Std.Data.Iterators.Consumers.Collect
910
import Std.Data.Iterators.Consumers.Loop
1011
import Std.Data.Iterators.Consumers.Partial
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/-
2+
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Paul Reichert
5+
-/
6+
prelude
7+
import Std.Data.Iterators.Consumers.Partial
8+
9+
namespace Std.Iterators
10+
11+
/--
12+
If possible, takes `n` steps with the iterator `it` and
13+
returns the `n`-th emitted value, or `none` if `it` finished
14+
before emitting `n` values.
15+
16+
This function requires a `Productive` instance proving that the iterator will always emit a value
17+
after a finite number of skips. If the iterator is not productive or such an instance is not
18+
available, consider using `it.allowNontermination.atIdxSlow?` instead of `it.atIdxSlow?`. However,
19+
it is not possible to formally verify the behavior of the partial variant.
20+
-/
21+
@[specialize]
22+
def Iter.atIdxSlow? {α β} [Iterator α Id β] [Productive α Id]
23+
(n : Nat) (it : Iter (α := α) β) : Option β :=
24+
match it.step with
25+
| .yield it' out _ =>
26+
match n with
27+
| 0 => some out
28+
| k + 1 => it'.atIdxSlow? k
29+
| .skip it' _ => it'.atIdxSlow? n
30+
| .done _ => none
31+
termination_by (n, it.finitelyManySkips)
32+
33+
/--
34+
If possible, takes `n` steps with the iterator `it` and
35+
returns the `n`-th emitted value, or `none` if `it` finished
36+
before emitting `n` values.
37+
38+
This is a partial, potentially nonterminating, function. It is not possible to formally verify
39+
its behavior. If the iterator has a `Productive` instance, consider using `Iter.atIdxSlow?` instead.
40+
-/
41+
@[specialize]
42+
partial def Iter.Partial.atIdxSlow? {α β} [Iterator α Id β] [Monad Id]
43+
(n : Nat) (it : Iter.Partial (α := α) β) : Option β := do
44+
match it.it.step with
45+
| .yield it' out _ =>
46+
match n with
47+
| 0 => some out
48+
| k + 1 => (⟨it'⟩ : Iter.Partial (α := α) β).atIdxSlow? k
49+
| .skip it' _ => (⟨it'⟩ : Iter.Partial (α := α) β).atIdxSlow? n
50+
| .done _ => none
51+
52+
end Std.Iterators

src/Std/Data/Iterators/Consumers/Monadic/Collect.lean

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ class LawfulIteratorCollect (α : Type w) (m : Type w → Type w') [Monad m] [It
9191
[i : IteratorCollect α m] where
9292
lawful : i = .defaultImplementation
9393

94+
theorem LawfulIteratorCollect.toArrayMapped_eq {α β γ : Type w} {m : Type w → Type w'} [Monad m]
95+
[Iterator α m β] [Finite α m] [IteratorCollect α m] [hl : LawfulIteratorCollect α m]
96+
{f : β → m γ} {it : IterM (α := α) m β} :
97+
IteratorCollect.toArrayMapped f it = IterM.DefaultConsumers.toArrayMapped f it := by
98+
cases hl.lawful; rfl
99+
94100
instance (α : Type w) (m : Type w → Type w') [Monad m] [Iterator α m β]
95101
[Monad m] [Iterator α m β] [Finite α m] :
96102
haveI : IteratorCollect α m := .defaultImplementation

src/Std/Data/Iterators/Lemmas.lean

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/-
2+
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Paul Reichert
5+
-/
6+
prelude
7+
import Std.Data.Iterators.Lemmas.Basic
8+
import Std.Data.Iterators.Lemmas.Monadic
9+
import Std.Data.Iterators.Lemmas.Consumers
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/-
2+
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Paul Reichert
5+
-/
6+
prelude
7+
import Std.Data.Iterators.Basic
8+
9+
namespace Std.Iterators
10+
11+
/--
12+
Induction principle for finite iterators: One can define a function `f` that maps every
13+
iterator `it` to an element of `motive it` by defining `f it` in terms of the values of `f` on
14+
the plausible successors of `it'.
15+
-/
16+
@[specialize]
17+
def Iter.inductSteps {α β} [Iterator α Id β] [Finite α Id]
18+
(motive : Iter (α := α) β → Sort x)
19+
(step : (it : Iter (α := α) β) →
20+
(ih_yield : ∀ {it' : Iter (α := α) β} {out : β},
21+
it.IsPlausibleStep (.yield it' out) → motive it') →
22+
(ih_skip : ∀ {it' : Iter (α := α) β}, it.IsPlausibleStep (.skip it') → motive it') →
23+
motive it)
24+
(it : Iter (α := α) β) : motive it :=
25+
step it
26+
(fun {it' _} _ => inductSteps motive step it')
27+
(fun {it'} _ => inductSteps motive step it')
28+
termination_by it.finitelyManySteps
29+
30+
/--
31+
Induction principle for productive iterators: One can define a function `f` that maps every
32+
iterator `it` to an element of `motive it` by defining `f it` in terms of the values of `f` on
33+
the plausible skip successors of `it'.
34+
-/
35+
@[specialize]
36+
def Iter.inductSkips {α β} [Iterator α Id β] [Productive α Id]
37+
(motive : Iter (α := α) β → Sort x)
38+
(step : (it : Iter (α := α) β) →
39+
(ih_skip : ∀ {it' : Iter (α := α) β}, it.IsPlausibleStep (.skip it') → motive it') →
40+
motive it)
41+
(it : Iter (α := α) β) : motive it :=
42+
step it (fun {it'} _ => inductSkips motive step it')
43+
termination_by it.finitelyManySkips
44+
45+
end Std.Iterators
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/-
2+
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Paul Reichert
5+
-/
6+
prelude
7+
import Std.Data.Iterators.Lemmas.Consumers.Monadic
8+
import Std.Data.Iterators.Lemmas.Consumers.Collect

0 commit comments

Comments
 (0)