lean4-htt/src/Std/Data/Iterators/Combinators/Monadic/TakeWhile.lean
Paul Reichert f58999a7a6
refactor: use Shrink stub in the iterator framework (#10725)
This PR introduces a no-op version of `Shrink`, a type that should allow
shrinking small types into smaller universes given a proof that the type
is small enough, and uses it in the iterator library. Because this type
would require special compiler support, the current version is just a
wrapper around the inner type so that the wrapper is equivalent, but not
definitionally equivalent.

While `Shrink` is unable to shrink universes right now, but introducing
it now will allow us to generalize the universes in the iterator library
with fewer breaking changes as soon as an actual `Shrink` is possible.
2025-10-14 10:22:14 +00:00

257 lines
10 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
module
prelude
public import Init.Data.Nat.Lemmas
public import Init.RCases
public import Init.Data.Iterators.Basic
public import Init.Data.Iterators.Consumers.Monadic.Collect
public import Init.Data.Iterators.Consumers.Monadic.Loop
public import Init.Data.Iterators.Internal.Termination
public import Init.Data.Iterators.PostconditionMonad
@[expose] public section
/-!
# Monadic `takeWhile` iterator combinator
This module provides the iterator combinator `IterM.takeWhile` that will take all values emitted
by a given iterator until a given predicate on these values becomes false the first time. Then
the combinator will terminate.
Several variants of this combinator are provided:
* `M` suffix: Instead of a pure function, this variant takes a monadic function. Given a suitable
`MonadLiftT` instance, it will also allow lifting the iterator to another monad first and then
applying the mapping function in this monad.
* `WithPostcondition` suffix: This variant takes a monadic function where the return type in the
monad is a subtype. This variant is in rare cases necessary for the intrinsic verification of an
iterator, and particularly for specialized termination proofs. If possible, avoid this.
-/
namespace Std.Iterators
variable {α : Type w} {m : Type w → Type w'} {β : Type w}
/--
Internal state of the `takeWhile` combinator. Do not depend on its internals.
-/
@[unbox]
structure TakeWhile (α : Type w) (m : Type w → Type w') (β : Type w)
(P : β → PostconditionT m (ULift Bool)) where
/-- Internal implementation detail of the iterator library. -/
inner : IterM (α := α) m β
/--
*Note: This is a very general combinator that requires an advanced understanding of monads,
dependent types and termination proofs. The variants `takeWhile` and `takeWhileM` are easier to use
and sufficient for most use cases.*
Given an iterator `it` and a monadic predicate `P`, `it.takeWhileWithPostcondition P` is an iterator
that emits the values emitted by `it` until one of those values is rejected by `P`.
If some emitted value is rejected by `P`, the value is dropped and the iterator terminates.
`P` is expected to return `PostconditionT m (ULift Bool)`. The `PostconditionT` transformer allows
the caller to intrinsically prove properties about `P`'s return value in the monad `m`, enabling
termination proofs depending on the specific behavior of `P`.
**Marble diagram (ignoring monadic effects):**
Assuming that the predicate `P` accepts `a` and `b` but rejects `c`:
```text
it ---a----b---c--d-e--⊥
it.takeWhileWithPostcondition P ---a----b---⊥
it ---a----⊥
it.takeWhileWithPostcondition P ---a----⊥
```
**Termination properties:**
* `Finite` instance: only if `it` is finite
* `Productive` instance: only if `it` is productive
Depending on `P`, it is possible that `it.takeWhileWithPostcondition P` is finite (or productive)
although `it` is not. In this case, the `Finite` (or `Productive`) instance needs to be proved
manually.
**Performance:**
This combinator calls `P` on each output of `it` until the predicate evaluates to false. Then
it terminates.
-/
@[always_inline, inline]
def IterM.takeWhileWithPostcondition (P : β → PostconditionT m (ULift Bool)) (it : IterM (α := α) m β) :=
(toIterM (TakeWhile.mk (P := P) it) m β : IterM m β)
/--
Given an iterator `it` and a monadic predicate `P`, `it.takeWhileM P` is an iterator that outputs
the values emitted by `it` until one of those values is rejected by `P`.
If some emitted value is rejected by `P`, the value is dropped and the iterator terminates.
If `P` is pure, then the simpler variant `takeWhile` can be used instead.
**Marble diagram (ignoring monadic effects):**
Assuming that the predicate `P` accepts `a` and `b` but rejects `c`:
```text
it ---a----b---c--d-e--⊥
it.takeWhileM P ---a----b---⊥
it ---a----⊥
it.takeWhileM P ---a----⊥
```
**Termination properties:**
* `Finite` instance: only if `it` is finite
* `Productive` instance: only if `it` is productive
Depending on `P`, it is possible that `it.takeWhileM P` is finite (or productive) although `it` is not.
In this case, the `Finite` (or `Productive`) instance needs to be proved manually.
**Performance:**
This combinator calls `P` on each output of `it` until the predicate evaluates to false. Then
it terminates.
-/
@[always_inline, inline]
def IterM.takeWhileM [Monad m] (P : β → m (ULift Bool)) (it : IterM (α := α) m β) :=
(it.takeWhileWithPostcondition (PostconditionT.lift ∘ P) : IterM m β)
/--
Given an iterator `it` and a predicate `P`, `it.takeWhile P` is an iterator that outputs
the values emitted by `it` until one of those values is rejected by `P`.
If some emitted value is rejected by `P`, the value is dropped and the iterator terminates.
In situations where `P` is monadic, use `takeWhileM` instead.
**Marble diagram (ignoring monadic effects):**
Assuming that the predicate `P` accepts `a` and `b` but rejects `c`:
```text
it ---a----b---c--d-e--⊥
it.takeWhile P ---a----b---⊥
it ---a----⊥
it.takeWhile P ---a----⊥
```
**Termination properties:**
* `Finite` instance: only if `it` is finite
* `Productive` instance: only if `it` is productive
Depending on `P`, it is possible that `it.takeWhile P` is finite (or productive) although `it` is not.
In this case, the `Finite` (or `Productive`) instance needs to be proved manually.
**Performance:**
This combinator calls `P` on each output of `it` until the predicate evaluates to false. Then
it terminates.
-/
@[always_inline, inline]
def IterM.takeWhile [Monad m] (P : β → Bool) (it : IterM (α := α) m β) :=
(it.takeWhileM (pure ∘ ULift.up ∘ P) : IterM m β)
/--
`it.PlausibleStep step` is the proposition that `step` is a possible next step from the
`takeWhile` iterator `it`. This is mostly internally relevant, except if one needs to manually
prove termination (`Finite` or `Productive` instances, for example) of a `takeWhile` iterator.
-/
inductive TakeWhile.PlausibleStep [Iterator α m β] {P} (it : IterM (α := TakeWhile α m β P) m β) :
(step : IterStep (IterM (α := TakeWhile α m β P) m β) β) → Prop where
| yield : ∀ {it' out}, it.internalState.inner.IsPlausibleStep (.yield it' out) →
(P out).Property (.up true) → PlausibleStep it (.yield (it'.takeWhileWithPostcondition P) out)
| skip : ∀ {it'}, it.internalState.inner.IsPlausibleStep (.skip it') →
PlausibleStep it (.skip (it'.takeWhileWithPostcondition P))
| done : it.internalState.inner.IsPlausibleStep .done → PlausibleStep it .done
| rejected : ∀ {it' out}, it.internalState.inner.IsPlausibleStep (.yield it' out) →
(P out).Property (.up false) → PlausibleStep it .done
@[always_inline, inline]
instance TakeWhile.instIterator [Monad m] [Iterator α m β] {P} :
Iterator (TakeWhile α m β P) m β where
IsPlausibleStep := TakeWhile.PlausibleStep
step it := do
match (← it.internalState.inner.step).inflate with
| .yield it' out h => match ← (P out).operation with
| ⟨.up true, h'⟩ => pure <| .deflate <| .yield (it'.takeWhileWithPostcondition P) out (.yield h h')
| ⟨.up false, h'⟩ => pure <| .deflate <| .done (.rejected h h')
| .skip it' h => pure <| .deflate <| .skip (it'.takeWhileWithPostcondition P) (.skip h)
| .done h => pure <| .deflate <| .done (.done h)
private def TakeWhile.instFinitenessRelation [Monad m] [Iterator α m β]
[Finite α m] {P} :
FinitenessRelation (TakeWhile α m β P) m where
rel := InvImage WellFoundedRelation.rel (IterM.finitelyManySteps ∘ TakeWhile.inner ∘ IterM.internalState)
wf := by
apply InvImage.wf
exact WellFoundedRelation.wf
subrelation {it it'} h := by
obtain ⟨step, h, h'⟩ := h
cases h'
case yield it' out k h' h'' =>
cases h
exact IterM.TerminationMeasures.Finite.rel_of_yield h'
case skip it' out h' =>
cases h
exact IterM.TerminationMeasures.Finite.rel_of_skip h'
case done _ =>
cases h
case rejected _ =>
cases h
instance TakeWhile.instFinite [Monad m] [Iterator α m β] [Finite α m] {P} :
Finite (TakeWhile α m β P) m :=
by exact Finite.of_finitenessRelation instFinitenessRelation
private def TakeWhile.instProductivenessRelation [Monad m] [Iterator α m β]
[Productive α m] {P} :
ProductivenessRelation (TakeWhile α m β P) m where
rel := InvImage WellFoundedRelation.rel (IterM.finitelyManySkips ∘ TakeWhile.inner ∘ IterM.internalState)
wf := by
apply InvImage.wf
exact WellFoundedRelation.wf
subrelation {it it'} h := by
cases h
exact IterM.TerminationMeasures.Productive.rel_of_skip _
instance TakeWhile.instProductive [Monad m] [Iterator α m β] [Productive α m] {P} :
Productive (TakeWhile α m β P) m :=
by exact Productive.of_productivenessRelation instProductivenessRelation
instance TakeWhile.instIteratorCollect [Monad m] [Monad n] [Iterator α m β] [Productive α m] {P} :
IteratorCollect (TakeWhile α m β P) m n :=
.defaultImplementation
instance TakeWhile.instIteratorCollectPartial [Monad m] [Monad n] [Iterator α m β] {P} :
IteratorCollectPartial (TakeWhile α m β P) m n :=
.defaultImplementation
instance TakeWhile.instIteratorLoop [Monad m] [Monad n] [Iterator α m β]
[IteratorLoop α m n] :
IteratorLoop (TakeWhile α m β P) m n :=
.defaultImplementation
instance TakeWhile.instIteratorForPartial [Monad m] [Monad n] [Iterator α m β]
[IteratorLoopPartial α m n] {P} :
IteratorLoopPartial (TakeWhile α m β P) m n :=
.defaultImplementation
instance {α : Type w} [Monad m] [Iterator α m β] [Finite α m] [IteratorLoop α m m] {P} :
IteratorSize (TakeWhile α m β P) m :=
.defaultImplementation
instance {α : Type w} [Monad m] [Iterator α m β] [IteratorLoopPartial α m m] {P} :
IteratorSizePartial (TakeWhile α m β P) m :=
.defaultImplementation
end Std.Iterators