Induction principle for productive iterators: One can define a function f that maps every
iterator it to an element of motive it by defining f it in terms of the values of f on
the plausible skip successors of `it'.
21.5. Reasoning About Iterators
21.5.1. Reasoning About Consumers
The iterator library provides a large number of useful lemmas.
Most theorems about finite iterators can be proven by rewriting the statement to one about lists, using the fact that the correspondence between iterator combinators and corresponding list operations has already been proved.
In practice, many of these theorems are already registered as simp lemmas.
The lemmas have a very predictable naming system, and many are in the default simp set. Some of the most important include:
-
Consumer lemmas such as
Iter.all_toList,Iter.any_toList, andIter.foldl_toListthat introduce lists as a model. -
Simplification lemmas such as
Iter.toList_mapthatIter.toList_filterpush the list model “inwards” in the goal. -
Producer lemmas such as
List.toList_iterandArray.toList_iterthat replace a producer with a list model, removing iterators from the goal entirely.
The latter two categories are typically automatic with simp.
Reasoning via Lists
Every element returned by an iterator that multiplies the numbers consumed some other iterator by two is even.
To prove this statement, Iter.all_toList, Iter.toList_map, and Array.toList_iter are used to replace the statement about iterators with one about lists, after which simp discharges the goal:
example (l : Array Nat) :
(l.iter.map (· * 2)).all (· % 2 = 0) := l:Array Nat⊢ Iter.all (fun x => decide (x % 2 = 0)) (Iter.map (fun x => x * 2) l.iter) = true
l:Array Nat⊢ ((Iter.map (fun x => x * 2) l.iter).toList.all fun x => decide (x % 2 = 0)) = true
l:Array Nat⊢ ((List.map (fun x => x * 2) l.iter.toList).all fun x => decide (x % 2 = 0)) = true
l:Array Nat⊢ ((List.map (fun x => x * 2) l.toList).all fun x => decide (x % 2 = 0)) = true
All goals completed! 🐙
In fact, because most of the needed lemmas are in the default simp set, the proof can be quite short:
example (l : Array Nat) :
(l.iter.map (· * 2)).all (· % 2 = 0) := l:Array Nat⊢ Iter.all (fun x => decide (x % 2 = 0)) (Iter.map (fun x => x * 2) l.iter) = true
All goals completed! 🐙
21.5.2. Stepwise Reasoning
When there are not enough lemmas to prove a property by rewriting to a list model, it can be necessary to prove things about iterators by reasoning directly about their step functions. The induction principles in this section are useful for stepwise reasoning.
Std.Iterators.Iter.inductSkips.{x, u_1} {α β : Type u_1} [Iterator α Id β] [Productive α Id] (motive : Iter β → Sort x) (step : (it : Iter β) → ({it' : Iter β} → it.IsPlausibleStep (IterStep.skip it') → motive it') → motive it) (it : Iter β) : motive itStd.Iterators.Iter.inductSkips.{x, u_1} {α β : Type u_1} [Iterator α Id β] [Productive α Id] (motive : Iter β → Sort x) (step : (it : Iter β) → ({it' : Iter β} → it.IsPlausibleStep (IterStep.skip it') → motive it') → motive it) (it : Iter β) : motive it
Std.Iterators.IterM.inductSkips.{x, u_1, u_2} {α : Type u_1} {m : Type u_1 → Type u_2} {β : Type u_1} [Iterator α m β] [Productive α m] (motive : IterM m β → Sort x) (step : (it : IterM m β) → ({it' : IterM m β} → it.IsPlausibleStep (IterStep.skip it') → motive it') → motive it) (it : IterM m β) : motive itStd.Iterators.IterM.inductSkips.{x, u_1, u_2} {α : Type u_1} {m : Type u_1 → Type u_2} {β : Type u_1} [Iterator α m β] [Productive α m] (motive : IterM m β → Sort x) (step : (it : IterM m β) → ({it' : IterM m β} → it.IsPlausibleStep (IterStep.skip it') → motive it') → motive it) (it : IterM m β) : motive it
Induction principle for productive monadic iterators: One can define a function f that maps every
iterator it to an element of motive it by defining f it in terms of the values of f on
the plausible skip successors of `it'.
Std.Iterators.Iter.inductSteps.{x, u_1} {α β : Type u_1} [Iterator α Id β] [Finite α Id] (motive : Iter β → Sort x) (step : (it : Iter β) → ({it' : Iter β} → {out : β} → it.IsPlausibleStep (IterStep.yield it' out) → motive it') → ({it' : Iter β} → it.IsPlausibleStep (IterStep.skip it') → motive it') → motive it) (it : Iter β) : motive itStd.Iterators.Iter.inductSteps.{x, u_1} {α β : Type u_1} [Iterator α Id β] [Finite α Id] (motive : Iter β → Sort x) (step : (it : Iter β) → ({it' : Iter β} → {out : β} → it.IsPlausibleStep (IterStep.yield it' out) → motive it') → ({it' : Iter β} → it.IsPlausibleStep (IterStep.skip it') → motive it') → motive it) (it : Iter β) : motive it
Induction principle for finite iterators: One can define a function f that maps every
iterator it to an element of motive it by defining f it in terms of the values of f on
the plausible successors of `it'.
Std.Iterators.IterM.inductSteps.{x, u_1, u_2} {α : Type u_1} {m : Type u_1 → Type u_2} {β : Type u_1} [Iterator α m β] [Finite α m] (motive : IterM m β → Sort x) (step : (it : IterM m β) → ({it' : IterM m β} → {out : β} → it.IsPlausibleStep (IterStep.yield it' out) → motive it') → ({it' : IterM m β} → it.IsPlausibleStep (IterStep.skip it') → motive it') → motive it) (it : IterM m β) : motive itStd.Iterators.IterM.inductSteps.{x, u_1, u_2} {α : Type u_1} {m : Type u_1 → Type u_2} {β : Type u_1} [Iterator α m β] [Finite α m] (motive : IterM m β → Sort x) (step : (it : IterM m β) → ({it' : IterM m β} → {out : β} → it.IsPlausibleStep (IterStep.yield it' out) → motive it') → ({it' : IterM m β} → it.IsPlausibleStep (IterStep.skip it') → motive it') → motive it) (it : IterM m β) : motive it
Induction principle for finite monadic iterators: One can define a function f that maps every
iterator it to an element of motive it by defining f it in terms of the values of f on
the plausible successors of `it'.
The standard library also includes lemmas for the stepwise behavior of all the producers and combinators.
Examples include List.step_iter_nil, List.step_iter_cons, IterM.step_map.
21.5.3. Monads for Reasoning
Std.Iterators.PostconditionT.{w, w'} (m : Type w → Type w') (α : Type w) : Type (max w w')Std.Iterators.PostconditionT.{w, w'} (m : Type w → Type w') (α : Type w) : Type (max w w')
PostconditionT m α represents an operation in the monad m together with a
intrinsic proof that some postcondition holds for the α valued monadic result.
It consists of a predicate P about α and an element of m ({ a // P a }) and is a helpful tool
for intrinsic verification, notably termination proofs, in the context of iterators.
PostconditionT m is a monad if m is. However, note that PostconditionT m α is a structure,
so that the compiler will generate inefficient code from recursive functions returning
PostconditionT m α. Optimizations for ReaderT, StateT etc. aren't applicable for structures.
Moreover, PostconditionT m α is not a well-behaved monad transformer because PostconditionT.lift
neither commutes with pure nor with bind.
Constructor
Std.Iterators.PostconditionT.mk.{w, w'}
Fields
Property : α → Prop
A predicate that holds for the return value(s) of the m-monadic operation.
operation : m (Subtype self.Property)
The actual monadic operation. Its return value is bundled together with a proof that
it satisfies Property.
Std.Iterators.PostconditionT.run.{w, w'} {m : Type w → Type w'} [Monad m] {α : Type w} (x : PostconditionT m α) : m αStd.Iterators.PostconditionT.run.{w, w'} {m : Type w → Type w'} [Monad m] {α : Type w} (x : PostconditionT m α) : m α
Converts an operation from PostConditionT m to m, discarding the postcondition.
Std.Iterators.PostconditionT.lift.{w, w'} {α : Type w} {m : Type w → Type w'} [Functor m] (x : m α) : PostconditionT m αStd.Iterators.PostconditionT.lift.{w, w'} {α : Type w} {m : Type w → Type w'} [Functor m] (x : m α) : PostconditionT m α
Lifts an operation from m to PostconditionT m without asserting any nontrivial postcondition.
Caution: lift is not a lawful lift function.
For example, pure a : PostconditionT m α is not the same as
PostconditionT.lift (pure a : m α).
Std.Iterators.PostconditionT.liftWithProperty.{w, w'} {α : Type w} {m : Type w → Type w'} {P : α → Prop} (x : m { α // P α }) : PostconditionT m αStd.Iterators.PostconditionT.liftWithProperty.{w, w'} {α : Type w} {m : Type w → Type w'} {P : α → Prop} (x : m { α // P α }) : PostconditionT m α
Lifts a monadic value from m { a : α // P a } to a value PostconditionT m α.
Asserts that a certain iterator it could plausibly yield the value out after an arbitrary
number of steps.
Constructors
direct.{w} {α β : Type w} [Iterator α Id β] {it : Iter β} {out : β} : it.IsPlausibleOutput out → it.IsPlausibleIndirectOutput out
indirect.{w} {α β : Type w} [Iterator α Id β] {it it' : Iter β} {out : β} : it'.IsPlausibleSuccessorOf it → it'.IsPlausibleIndirectOutput out → it.IsPlausibleIndirectOutput out
Std.Iterators.HetT.{w, w', v} (m : Type w → Type w') (α : Type v) : Type (max v w')Std.Iterators.HetT.{w, w', v} (m : Type w → Type w') (α : Type v) : Type (max v w')
If m is a monad, then HetT m is a monad that has two features:
-
It generalizes
mto arbitrary universes. -
It tracks a postcondition property that holds for the monadic return value, similarly to
PostconditionT.
This monad is noncomputable and is merely a vehicle for more convenient proofs, especially proofs about the equivalence of iterators, because it avoids universe issues and spares users the work to handle the postconditions manually.
Caution: Just like PostconditionT, this is not a lawful monad transformer.
To lift from m to HetT m, use HetT.lift.
Because this monad is fundamentally universe-polymorphic, it is recommended for consistency to
always use the methods HetT.pure, HetT.map and HetT.bind instead of the homogeneous versions
Pure.pure, Functor.map and Bind.bind.
Constructor
Std.Iterators.HetT.mk.{w, w', v}
Fields
Property : α → Prop
A predicate that holds for the return value(s) of the m-monadic operation.
small : Std.Internal.Small (Subtype self.Property)
A proof that the possible return values are equivalent to a w-small type.
operation : m (Std.Internal.USquash (Subtype self.Property))
The actual monadic operation. Its return value is bundled together with a proof that
it satisfies Property and squashed so that it fits into the monad m.
A noncomputable variant of IterM.step using the HetT monad.
It is used in the definition of the equivalence relations on iterators,
namely IterM.Equiv and Iter.Equiv.
Lifts x : m α into HetT m α with the trivial postcondition.
Caution: This is not a lawful monad lifting function
Applies the given function to the result of the contained m-monadic operation with a
proof that the postcondition property holds, returning another operation in m.
A universe-heterogeneous version of Functor.map.
A generalization of HetT.map that provides the postcondition property to the mapping function.
A universe-heterogeneous version of Bind.bind.
21.5.4. Equivalence
Iterator equivalence is defined in terms of the observable behavior of iterators, rather than their implementations. In particular, the internal state is ignored.
Equivalence relation on iterators. Equivalent iterators behave the same as long as the internal state of them is not directly inspected.
Two iterators (possibly of different types) are equivalent if they have the same
Iterator.IsPlausibleStep relation and their step functions are the same up to equivalence of the
successor iterators. This coinductive definition captures the idea that the only relevant feature
of an iterator is its step function. Other information retrievable from the iterator -- for example,
whether it is a list iterator or an array iterator -- is totally irrelevant for the equivalence
judgement.
Equivalence relation on monadic iterators. Equivalent iterators behave the same as long as the internal state of them is not directly inspected.
Two iterators (possibly of different types) are equivalent if they have the same
Iterator.IsPlausibleStep relation and their step functions are the same up to equivalence of the
successor iterators. This coinductive definition captures the idea that the only relevant feature
of an iterator is its step function. Other information retrievable from the iterator -- for example,
whether it is a list iterator or an array iterator -- is totally irrelevant for the equivalence
judgement.