Conversation
Repeat type for verified repeat/while loopsrepeat/while loops
|
Mathlib CI status (docs):
|
|
Reference manual CI status:
|
8cf008c to
6cbb299
Compare
|
Some food for thought; There are some variants of -- 1. loop only terminates when the initial value satisfies some invariant (here `i ≤ x`)
-- We basically get this one for free by swapping out `extrinsicFix` for `partialExtrinsicFix`
def loopWithTerminationPrecond (x : Nat) : Id Nat := do
let mut i := 0
while i ≠ x do
i := i + 1
return i
example : (loopWithTerminationPrecond x).run = x := by sorry
-- 2. loop only terminates when the initial *state* satisfies some invariant (here `(← get) ≤ x`)
-- I'm not quite sure how to tackle this one...
def loopWithStatefulTerminationPrecond (x : Nat) : StateM Nat Nat := do
set 0
while (← get) ≠ x do
modify fun i => i + 1
get
example : (loopWithStatefulTerminationPrecond x).run' state = x := by sorry
-- 3. loop may not terminate; but we are in `Option`
-- similar to 1. but this time we want non-termination to provide meaningful output (i.e. `none`)
-- in this case it's enough to take the unique fixpoint of `fun cont : Nat → Option Nat => if x ≠ 20 then cont (x + 1) else pure x`
def possiblyDivergentLoop (x : Nat) : Option Nat := do
let mut x := x
while x ≠ 20 do
x := x + 1
return x
example : possiblyDivergentLoop x = some n → n = 20 ∧ x ≤ n := by sorry
inductive SpecialM (α : Type) where
| ok (val : α)
| error (err : String)
| diverge
instance : Monad SpecialM where
pure := .ok
bind
| .ok val, f => f val
| .error err, _ => .error err
| .diverge, _ => .diverge
-- ... setting up more instances for `SpecialM`
-- 4. loop may not terminate; but we have a special monad
-- similar to 3. but just requiring a fixpoint is not enough
-- since `error` and `diverge` are not distinguishable just from the `Monad` instance
def specialDivergence (x : Nat) : SpecialM Nat := do
let mut x := x
while x ≠ 20 do
x := x + 1
return x
example : specialDivergence 21 = .diverge := by sorryPossibly an idea would be a special |
6cbb299 to
756055c
Compare
This PR introduces a `Repeat` type backed by `partial_fixpoint` and a new `MonoBindRight` class, enabling compositionally verified `repeat`/`while` loops via `mvcgen`. Unlike the previous `Loop`-based encoding (which uses `partial`), `Repeat.forIn` is defined via `partial_fixpoint` and supports extrinsic verification through `@[spec]` theorems. Key components: - `MonoBindRight`: a class weaker than `MonoBind` that only requires bind monotonicity in the continuation argument, with instances for all standard monads (Id, StateT, ExceptT, OptionT, ReaderT, ST, EST, EIO, IO, etc.) - `Repeat.forIn`: loop body using `partial_fixpoint` recursion - `WhileVariant`/`WhileInvariant`: stateful termination measures and invariants - `Spec.forIn_repeat`: the `@[spec]` theorem for verified while loops - `SPred.evalsTo`: helper for reasoning about stateful variant evaluation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
756055c to
76486ba
Compare
|
@Rob23oba although the PR body doesn't reflect it yet, I followed through on an idea by @datokrat and rebased the entire approach on def Total {β : Type u} {m : Type u → Type v} {ps} [Nonempty β] [Monad m] [WP m ps] (body : Unit → β → m (ForInStep β)) :=
∃ (f : β → β), ∀ [MonoBindRight m] b,
⦃⌜True⌝⦄ Repeat.forIn b body ⦃⇓ r => ⌜f b = r⌝⦄ |
| class MonoBindRight (m : Type u → Type v) [Bind m] where | ||
| /-- Every `m β` with `Nonempty β` has a chain-complete partial order. -/ | ||
| instCCPO β [Nonempty β] : CCPO (m β) | ||
| /-- Bind is monotone in the second (continuation) argument. -/ | ||
| bind_mono_right {a : m α} {f₁ f₂ : α → m β} [Nonempty β] (h : ∀ x, f₁ x ⊑ f₂ x) : | ||
| a >>= f₁ ⊑ a >>= f₂ |
There was a problem hiding this comment.
Very optional suggestion: We could call it MonadTail or something, emphasizing that the continuation is a tail call and making it sound less similar to the propositional MonoBind?
There was a problem hiding this comment.
Yes, I thought about that a bit as well. Maybe we can even generalize the theorem a bit because I don't think the property is much related to PartialOrder at all... Very roughly, something like
preserves {a : m α} {f₁ f₂ : α → m β} {R : m β → m β → Prop} (h : ∀ x, R (f₁ x) (f₂ x)) : R (a >>= f₁) (a >>= f₂)
should hold (for certain R); it reeks of parametricity/logical relations. But perhaps the exact details are too complicated to work out and the ad-hoc thing is OK for now.
Anyway, all on board with MonadTail. Waiting for Claude quota...
Replace the `extrinsicFix`/`MonadAttach` approach with Lean's existing `partial_fixpoint` mechanism and a new `MonadTail` class (weaker than `MonoBind`, requiring only bind monotonicity in the continuation). Key changes: - Rename `MonoBindRight` to `MonadTail` with scoped monotonicity attribute - Rewrite `Repeat.forIn` using `partial_fixpoint` recursion - Move `RepeatVariant`/`RepeatInvariant` to `RepeatSpec.lean` - Add `MonadTail` instances for `BaseAsync`, `EAsync`, `ContextAsync` - Add axiom `BaseAsync.bind_mono_right'` for opaque `BaseIO.bindTask` - Simplify `elabDoRepeat` (remove try/catch fallback) - Remove `WPAdequacy`, `MonadAttach` instances, `RepeatCursor` Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Hmm what about extrinsic @[specialize]
partial def Lean.Order.opaqueFix {α : Sort u} [Lean.Order.CCPO α] (f : α → α) : α :=
f (opaqueFix f)
open scoped Classical in
@[implemented_by opaqueFix]
def Lean.Order.extrinsicFix {α : Sort u} [Lean.Order.CCPO α] (f : α → α) : α :=
if h : monotone f then
fix f h
else
opaqueFix f
@[inline, always_inline]
def Repeat.forIn {β : Type u} {m : Type u → Type v}
[Monad m] [Lean.Order.CCPO (m β)] (b : β) (f : Unit → β → m (ForInStep β)) : m β :=
Lean.Order.extrinsicFix (α := (b : β) → m β) (b := b) fun cont b => do
match ← f () b with
| .done val => pure val
| .yield val => cont valOne advantage of that is that we can also apply it where we don't have a proof of monotonicity (i.e. @[inline, always_inline]
def repeatNew {m : Type u → Type v} [Lean.Order.CCPO (m α)] (f : (cont : σ → m α) → σ → m α)
(init : σ) : m α :=
Lean.Order.extrinsicFix f initEdit: Also, just to mention, I think |
This PR introduces
Lean.Repeat, a well-founded alternative toLean.Loopforrepeat/whileloops. UnlikeLoop, which usespartialrecursion (making loop bodies opaque to the proof system),Repeatis implemented usingWellFounded.extrinsicFixandMonadAttach, enabling compositional verification of loop programs viamvcgen.When the new behavior is active (default),
repeat/whileloops expand tofor _ in Repeat.mk do ...instead offor _ in Loop.mk do .... Thebackward.do.whileoption (defaultfalse) reverts to the legacyLoop.mkbehavior.New files:
src/Init/Repeat.lean:Repeattype,Repeat.Relstep relation,Repeat.forInusingextrinsicFix, and override macrosrc/Lean/Elab/BuiltinDo/Repeat.lean: builtin do-element elaborator dispatching onbackward.do.whilesrc/Std/Do/WP/Adequacy.lean:WPAdequacyclass linkingMonadAttach.CanReturnwith WP proofssrc/Std/Do/Triple/RepeatSpec.lean:@[spec]theoremSpec.forIn_repeatformvcgentests/elab/repeat_extrinsic.lean: verifiedsqrtexample and option testsModified files add supporting infrastructure:
backward.do.whileoption registration, legacy elaborator support, SPred lemmas (exists_prop_of_true,forall_prop_of_true), spec lemmas (Spec.attach,Repeat.Rel.of_wp,RepeatCursor/RepeatInvariant/RepeatVarianttypes), andMonadAttachinstances forBaseAsync/EAsync.🤖 Generated with Claude Code