Compare commits

...

10 Commits

Author SHA1 Message Date
Sebastian Graf
0d7b72c971 chore: add test case from Rob 2026-04-08 16:19:22 +00:00
Sebastian Graf
f89b8fc652 Revert "follow an interesting idea where MonadTail preserves arbitrary logical relations"
This reverts commit 2198b357f6.
2026-04-08 15:49:45 +00:00
Sebastian Graf
2198b357f6 follow an interesting idea where MonadTail preserves arbitrary logical relations
The idea doesn't work because of the instance for StateT, where we need
`CCPO (α x σ)` rather than `CCPO α`. Pity.
2026-04-08 15:49:41 +00:00
Sebastian Graf
4281e7870d chore: remove extrinsicFix_induct as only the constantly true motive is admissible for all CCPOs 2026-04-07 17:16:28 +00:00
Sebastian Graf
60fe176ff8 chore: add Robin Arnez as co-author of ExtrinsicFix
Co-Authored-By: Robin Arnez <152706811+Rob23oba@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 16:49:29 +00:00
Sebastian Graf
691708e7dc doc: improve docstrings for extrinsicFix to match WFExtrinsicFix style
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 16:45:37 +00:00
Sebastian Graf
02b282f208 refactor: replace Repeat with extrinsicFix-based Loop.forIn
Replace the `Repeat` type and its `partial_fixpoint`/`MonadTail`-based `forIn` with an
`extrinsicFix` combinator applied directly to `Loop.forIn`. This eliminates the separate
`Repeat` type while making all `while`/`repeat` loops verifiable when `MonadTail` is
available — without requiring it at definition time.

`Lean.Order.extrinsicFix` is modeled on `WellFounded.extrinsicFix`: at runtime it iterates
`f` via `opaqueFix`, but logically it equals `CCPO.fix` when a CCPO + monotonicity witness
exists (checked via classical choice). The `extrinsicFix_eq` theorem provides the unfold
equation, and `extrinsicFix_induct` provides fixpoint induction with an admissibility
hypothesis universally quantified over CCPO instances.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 16:41:19 +00:00
Sebastian Graf
78b4bd2221 refactor: simplify Repeat loop infrastructure
This commit removes the `backward.do.while` option and the dedicated BuiltinDo
elaborator for `repeat`, replacing them with a simpler design: a low-priority
fallback `ForIn` instance for monads without `MonadTail` that delegates to
`Loop.forIn`, and a single macro in `Init.Repeat` that always expands `repeat`
to `for _ in Repeat.mk do ...`. Instance resolution then picks the right `ForIn`
implementation based on whether the monad has a `MonadTail` instance.

Also removes axiom-based `MonadTail` instances for `BaseAsync`, `EAsync`, and
`ContextAsync`, and adds `evalsTo_3`/`_4`/`_5` simp lemmas.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 13:18:13 +00:00
Sebastian Graf
11c1b1406a refactor: use partial_fixpoint/MonadTail for Repeat loops
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>
2026-04-02 11:52:16 +00:00
Sebastian Graf
76486ba437 feat: add well-founded Repeat type for verified repeat/while loops
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>
2026-04-02 10:26:26 +00:00
11 changed files with 549 additions and 6 deletions

View File

@@ -7,5 +7,7 @@ module
prelude
public import Init.Internal.Order.Basic
public import Init.Internal.Order.ExtrinsicFix
public import Init.Internal.Order.Lemmas
public import Init.Internal.Order.MonadTail
public import Init.Internal.Order.Tactic

View File

@@ -0,0 +1,96 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sebastian Graf, Robin Arnez
-/
module
prelude
public import Init.Internal.Order.Basic
public import Init.Internal.Order.MonadTail
import Init.Classical
set_option linter.missingDocs true
/-!
This module provides a fixpoint combinator that combines advantages of `partial` and
`partial_fixpoint` recursion.
The combinator is similar to {lean}`CCPO.fix`, but does not require a CCPO instance or
monotonicity proof at the definition site. Therefore, it can be used in situations in which
these constraints are unavailable (e.g., when the monad does not have a `MonadTail` instance).
Given a CCPO and monotonicity proof, there are theorems guaranteeing that it equals
{lean}`CCPO.fix` and satisfies fixpoint induction.
-/
public section
namespace Lean.Order
/--
The function implemented as the loop {lean}`opaqueFix f = f (opaqueFix f)`.
{lean}`opaqueFix f` is the fixpoint of {name}`f`, as long as `f` is monotone with respect to
some CCPO on `α`.
The loop might run forever depending on {name}`f`. It is opaque, i.e., it is impossible to prove
nontrivial properties about it.
-/
@[specialize]
partial def opaqueFix {α : Sort u} [Nonempty α] (f : α α) : α :=
f (opaqueFix f)
/-
SAFE assuming that the code generated by iteration over `f` is equivalent to the CCPO
fixpoint of `f` if there exists a CCPO making `f` monotone.
-/
open _root_.Classical in
/--
A fixpoint combinator that can be used to construct recursive definitions with an *extrinsic*
proof of monotonicity.
Given a fixpoint functional {name}`f`, {lean}`extrinsicFix f` is the recursive function obtained
by having {name}`f` call itself recursively.
If there is no CCPO on `α` making {name}`f` monotone, {lean}`extrinsicFix f` might run forever.
In this case, nothing interesting can be proved about the result; it is opaque.
If there _is_ a CCPO on `α` making {name}`f` monotone, {lean}`extrinsicFix f` is equivalent to
{lean}`CCPO.fix f`, logically and regarding its termination behavior.
-/
@[cbv_opaque, implemented_by opaqueFix]
def extrinsicFix {α : Sort u} [Nonempty α] (f : α α) : α :=
if h : (inst : CCPO α), (letI := inst; monotone f) then
@fix α h.choose f h.choose_spec
else
-- Return `opaqueFix f` so that `implemented_by opaqueFix` is sound.
-- In effect, `extrinsicFix` is opaque if no CCPO makes `f` monotone.
opaqueFix f
/--
A fixpoint combinator that allows for deferred proofs of monotonicity.
{lean}`extrinsicFix f` is a function implemented as the loop
{lean}`extrinsicFix f = f (extrinsicFix f)`.
If there is a CCPO making `f` monotone, {name}`extrinsicFix_eq` proves that it satisfies the
fixpoint equation, and {name}`extrinsicFix_induct` enables fixpoint induction.
Otherwise, {lean}`extrinsicFix f` is opaque, i.e., it is impossible to prove nontrivial
properties about it.
-/
add_decl_doc extrinsicFix
/-- Every CCPO has at least one element (the bottom element). -/
noncomputable local instance CCPO.instNonempty [CCPO α] : Nonempty α := bot
/--
The fixpoint equation for `extrinsicFix`: given a CCPO instance and monotonicity of `f`,
{lean}`extrinsicFix f = f (extrinsicFix f)`.
-/
theorem extrinsicFix_eq [inst : CCPO α] {f : α α}
(hf : monotone f) :
extrinsicFix f = f (extrinsicFix f) := by
have h : inst' : CCPO α, (letI := inst'; monotone f) := inst, hf
simp only [extrinsicFix, dif_pos h]
exact @fix_eq α h.choose f h.choose_spec
end Lean.Order

View File

@@ -0,0 +1,140 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sebastian Graf
-/
module
prelude
public import Init.Internal.Order.Basic
import all Init.System.ST -- for EST.bind in MonadTail instance
set_option linter.missingDocs true
public section
namespace Lean.Order
/--
A *tail monad* is a monad whose bind operation preserves any suitable ordering of the continuation.
Specifically, `MonadTail m` asserts that every `m β` carries a chain-complete partial order (CCPO)
and that `>>=` is monotone in its second (continuation) argument with respect to that order.
This is a weaker requirement than `MonoBind`, which requires monotonicity in both arguments.
`MonadTail` is sufficient for `partial_fixpoint`-based recursive definitions where the
recursive call only appears in the continuation (second argument) of `>>=`.
-/
class MonadTail (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₂
attribute [implicit_reducible] MonadTail.instCCPO
attribute [instance] MonadTail.instCCPO
@[scoped partial_fixpoint_monotone]
theorem MonadTail.monotone_bind_right
(m : Type u Type v) [Monad m] [MonadTail m]
{α β : Type u} [Nonempty β]
{γ : Sort w} [PartialOrder γ]
(f : m α) (g : γ α m β)
(hmono : monotone g) :
monotone (fun (x : γ) => f >>= g x) :=
fun _ _ h => MonadTail.bind_mono_right (hmono _ _ h)
instance : MonadTail Id where
instCCPO _ := inferInstanceAs (CCPO (FlatOrder (b := Classical.ofNonempty)))
bind_mono_right h := h _
instance {σ : Type u} {m : Type u Type v} [Monad m] [MonadTail m] [Nonempty σ] :
MonadTail (StateT σ m) where
instCCPO α := inferInstanceAs (CCPO (σ m (α × σ)))
bind_mono_right h := by
intro s
show StateT.bind _ _ s StateT.bind _ _ s
simp only [StateT.bind]
apply MonadTail.bind_mono_right (m := m)
intro x, s'
exact h x s'
instance {ε : Type u} {m : Type u Type v} [Monad m] [MonadTail m] :
MonadTail (ExceptT ε m) where
instCCPO β := MonadTail.instCCPO (Except ε β)
bind_mono_right h := by
apply MonadTail.bind_mono_right (m := m)
intro x
cases x with
| error => exact PartialOrder.rel_refl
| ok a => exact h a
instance : MonadTail (Except ε) where
instCCPO β := inferInstanceAs (CCPO (FlatOrder (b := Classical.ofNonempty)))
bind_mono_right h := by
cases Except _ _ with
| error => exact FlatOrder.rel.refl
| ok a => exact h a
instance {m : Type u Type v} [Monad m] [MonadTail m] :
MonadTail (OptionT m) where
instCCPO β := MonadTail.instCCPO (Option β)
bind_mono_right h := by
apply MonadTail.bind_mono_right (m := m)
intro x
cases x with
| none => exact PartialOrder.rel_refl
| some a => exact h a
instance : MonadTail Option where
instCCPO _ := inferInstance
bind_mono_right h := MonoBind.bind_mono_right h
instance {ρ : Type u} {m : Type u Type v} [Monad m] [MonadTail m] :
MonadTail (ReaderT ρ m) where
instCCPO α := inferInstanceAs (CCPO (ρ m α))
bind_mono_right h := by
intro r
show ReaderT.bind _ _ r ReaderT.bind _ _ r
simp only [ReaderT.bind]
apply MonadTail.bind_mono_right (m := m)
intro x
exact h x r
set_option linter.missingDocs false in
noncomputable def ST.bot' [Nonempty α] (s : Void σ) : @FlatOrder (ST.Out σ α) (.mk Classical.ofNonempty (Classical.choice s)) :=
.mk Classical.ofNonempty (Classical.choice s)
instance [Nonempty α] : CCPO (ST σ α) where
rel := PartialOrder.rel (α := s, FlatOrder (ST.bot' s))
rel_refl := PartialOrder.rel_refl
rel_antisymm := PartialOrder.rel_antisymm
rel_trans := PartialOrder.rel_trans
has_csup hchain := CCPO.has_csup (α := s, FlatOrder (ST.bot' s)) hchain
instance : MonadTail (ST σ) where
instCCPO _ := inferInstance
bind_mono_right {_ _ a f₁ f₂} _ h := by
intro w
change FlatOrder.rel (ST.bind a f₁ w) (ST.bind a f₂ w)
simp only [ST.bind]
apply h
instance : MonadTail BaseIO :=
inferInstanceAs (MonadTail (ST IO.RealWorld))
instance [Nonempty ε] : MonadTail (EST ε σ) where
instCCPO _ := inferInstance
bind_mono_right h := MonoBind.bind_mono_right h
instance [Nonempty ε] : MonadTail (EIO ε) :=
inferInstanceAs (MonadTail (EST ε IO.RealWorld))
instance : MonadTail IO :=
inferInstanceAs (MonadTail (EIO IO.Error))
instance {ω : Type} {σ : Type} {m : Type Type} [Monad m] [MonadTail m] :
MonadTail (StateRefT' ω σ m) :=
inferInstanceAs (MonadTail (ReaderT (ST.Ref ω σ) m))
end Lean.Order

View File

@@ -7,6 +7,7 @@ module
prelude
public import Init.Core
public import Init.Internal.Order.ExtrinsicFix
public section
@@ -21,18 +22,38 @@ namespace Lean
inductive Loop where
| mk
open Lean.Order in
@[inline]
partial def Loop.forIn {β : Type u} {m : Type u Type v} [Monad m] (_ : Loop) (init : β) (f : Unit β m (ForInStep β)) : m β :=
let rec @[specialize] loop (b : β) : m β := do
def Loop.forIn {β : Type u} {m : Type u Type v} [Monad m]
(_ : Loop) (init : β) (f : Unit β m (ForInStep β)) : m β :=
haveI : Nonempty (β m β) := fun b => pure b
Lean.Order.extrinsicFix (fun (cont : β m β) (b : β) => do
match f () b with
| ForInStep.done b => pure b
| ForInStep.yield b => loop b
loop init
| .done val => pure val
| .yield val => cont val) init
instance [Monad m] : ForIn m Loop Unit where
forIn := Loop.forIn
syntax "repeat " doSeq : doElem
open Lean.Order in
theorem Loop.forIn_eq [Monad m] [MonadTail m]
{l : Loop} {b : β} {f : Unit β m (ForInStep β)} :
Loop.forIn l b f = (do
match f () b with
| .done val => pure val
| .yield val => Loop.forIn l val f) := by
haveI : Nonempty β := b
simp only [Loop.forIn]
apply congrFun
apply extrinsicFix_eq
intro cont₁ cont₂ h b'
apply MonadTail.bind_mono_right
intro r
cases r with
| done => exact PartialOrder.rel_refl
| yield val => exact h val
syntax (name := doRepeat) "repeat " doSeq : doElem
macro_rules
| `(doElem| repeat $seq) => `(doElem| for _ in Loop.mk do $seq)

View File

@@ -1780,6 +1780,10 @@ mutual
doIfToCode doElem doElems
else if k == ``Parser.Term.doUnless then
doUnlessToCode doElem doElems
else if k == `Lean.doRepeat then
let seq := doElem[1]
let expanded `(doElem| for _ in Loop.mk do $seq)
doSeqToCode (expanded :: doElems)
else if k == ``Parser.Term.doFor then withFreshMacroScope do
doForToCode doElem doElems
else if k == ``Parser.Term.doMatch then

View File

@@ -178,6 +178,48 @@ theorem entails_pure_elim_cons {σ : Type u} [Inhabited σ] (P Q : Prop) : entai
@[simp] theorem entails_4 {P Q : SPred [σ₁, σ₂, σ₃, σ₄]} : SPred.entails P Q ( s₁ s₂ s₃ s₄, (P s₁ s₂ s₃ s₄).down (Q s₁ s₂ s₃ s₄).down) := iff_of_eq rfl
@[simp] theorem entails_5 {P Q : SPred [σ₁, σ₂, σ₃, σ₄, σ₅]} : SPred.entails P Q ( s₁ s₂ s₃ s₄ s₅, (P s₁ s₂ s₃ s₄ s₅).down (Q s₁ s₂ s₃ s₄ s₅).down) := iff_of_eq rfl
/-!
# `SPred.evalsTo`
Relates a stateful value `SVal σs α` to a pure value `a : α`, lifting equality through the state.
-/
/-- Relates a stateful value to a pure value, lifting equality through the state layers. -/
def evalsTo {α : Type u} {σs : List (Type u)} (f : SVal σs α) (a : α) : SPred σs :=
match σs with
| [] => a = f
| _ :: _ => fun s => evalsTo (f s) a
@[simp, grind =] theorem evalsTo_nil {f : SVal [] α} {a : α} :
evalsTo f a = a = f := rfl
theorem evalsTo_cons {f : σ SVal σs α} {a : α} {s : σ} :
evalsTo (σs := σ::σs) f a s = evalsTo (f s) a := rfl
@[simp, grind =] theorem evalsTo_1 {f : SVal [σ] α} {a : α} {s : σ} :
evalsTo f a s = evalsTo (f s) a := rfl
@[simp, grind =] theorem evalsTo_2 {f : SVal [σ₁, σ₂] α} {a : α} {s₁ : σ₁} {s₂ : σ₂} :
evalsTo f a s₁ s₂ = evalsTo (f s₁ s₂) a := rfl
@[simp, grind =] theorem evalsTo_3 {f : SVal [σ₁, σ₂, σ₃] α} {a : α}
{s₁ : σ₁} {s₂ : σ₂} {s₃ : σ₃} :
evalsTo f a s₁ s₂ s₃ = evalsTo (f s₁ s₂ s₃) a := rfl
@[simp, grind =] theorem evalsTo_4 {f : SVal [σ₁, σ₂, σ₃, σ₄] α} {a : α}
{s₁ : σ₁} {s₂ : σ₂} {s₃ : σ₃} {s₄ : σ₄} :
evalsTo f a s₁ s₂ s₃ s₄ = evalsTo (f s₁ s₂ s₃ s₄) a := rfl
@[simp, grind =] theorem evalsTo_5 {f : SVal [σ₁, σ₂, σ₃, σ₄, σ₅] α} {a : α}
{s₁ : σ₁} {s₂ : σ₂} {s₃ : σ₃} {s₄ : σ₄} {s₅ : σ₅} :
evalsTo f a s₁ s₂ s₃ s₄ s₅ = evalsTo (f s₁ s₂ s₃ s₄ s₅) a := rfl
theorem evalsTo_total {P : SPred σs} (f : SVal σs α) :
P m, evalsTo f m := by
induction σs with
| nil => simp
| cons _ _ ih => intro s; apply ih
/-! # Tactic support -/
namespace Tactic
@@ -338,3 +380,4 @@ theorem Frame.frame {P Q T : SPred σs} {φ : Prop} [HasFrame P Q φ]
· exact HasFrame.reassoc.mp.trans SPred.and_elim_r
· intro hp
exact HasFrame.reassoc.mp.trans (SPred.and_elim_l' (h hp))

View File

@@ -7,6 +7,7 @@ module
prelude
public import Std.Do.SPred.Notation
import Init.PropLemmas
@[expose] public section
@@ -157,3 +158,17 @@ theorem imp_curry {P Q : SVal.StateTuple σs → Prop} : (SVal.curry (fun t =>
induction σs
case nil => rfl
case cons σ σs ih => intro s; simp only [imp_cons, SVal.curry_cons]; exact ih
/-! # Prop-indexed quantifiers -/
/-- Simplifies an existential over a true proposition. -/
theorem exists_prop_of_true {p : Prop} (h : p) {P : p SPred σs} : spred( (h : p), P h) = P h := by
induction σs with
| nil => ext; exact _root_.exists_prop_of_true h
| cons σ σs ih => ext; exact ih
/-- Simplifies a universal over a true proposition. -/
theorem forall_prop_of_true {p : Prop} (h : p) {P : p SPred σs} : spred( (h : p), P h) = P h := by
induction σs with
| nil => ext; exact _root_.forall_prop_of_true h
| cons σ σs ih => ext; exact ih

View File

@@ -0,0 +1,94 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sebastian Graf
-/
module
prelude
public import Std.Do.Triple.SpecLemmas
import Std.Tactic.Do.Syntax
set_option linter.missingDocs true
@[expose] public section
namespace Std.Do
/-!
# Specification theorem for `Loop`-based `repeat`/`while` loops
This file contains the `@[spec]` theorem for `forIn` over `Lean.Loop`, which enables
verified reasoning about `repeat`/`while` loops using `mvcgen`.
-/
set_option mvcgen.warning false
/-- A variant (termination measure) for a `repeat`/`while` loop. -/
@[spec_invariant_type]
abbrev RepeatVariant (β : Type u) (ps : PostShape.{u}) := β SVal ps.args (ULift Nat)
set_option linter.missingDocs false in
abbrev RepeatVariant.eval {β ps} (variant : RepeatVariant β ps) (b : β) (n : Nat) :=
SPred.evalsTo (variant b) n
/-- An invariant for a `repeat`/`while` loop. -/
@[spec_invariant_type]
abbrev RepeatInvariant β ps := PostCond (Bool × β) ps
section
variable {β : Type u} {m : Type u Type v} {ps : PostShape.{u}}
private theorem RepeatVariant.eval_total {P : SPred ps.args} (variant : RepeatVariant β ps) (b : β) :
P m, RepeatVariant.eval variant b m := by
mintro _
mhave h2 := SPred.evalsTo_total (variant b)
mcases h2 with m, h2
mexists m.down
private theorem RepeatVariant.add_eval {P Q : SPred ps.args} (variant : RepeatVariant β ps) (b : β)
(h : spred( m, RepeatVariant.eval variant b m P) Q) : P Q := by
apply SPred.entails.trans _ h
mintro _
mhave h2 := RepeatVariant.eval_total variant b
mcases h2 with m, h2
mexists m
mconstructor <;> massumption
end
section
variable {β : Type u} {m : Type u Type v} {ps : PostShape.{u}}
variable [Monad m] [Lean.Order.MonadTail m] [WPMonad m ps]
@[spec]
theorem Spec.forIn_loop
{l : _root_.Lean.Loop} {init : β} {f : Unit β m (ForInStep β)}
(measure : RepeatVariant β ps)
(inv : RepeatInvariant β ps)
(step : b mb,
Triple (f () b)
spred(RepeatVariant.eval measure b mb inv.1 (false, b))
(fun r => match r with
| .yield b' => spred( mb', RepeatVariant.eval measure b' mb' mb' < mb inv.1 (false, b'))
| .done b' => inv.1 (true, b'), inv.2)) :
Triple (forIn l init f) spred(inv.1 (false, init)) (fun b => inv.1 (true, b), inv.2) := by
haveI : Nonempty β := init
simp only [forIn]
apply RepeatVariant.add_eval measure init
apply SPred.exists_elim
intro minit
induction minit using Nat.strongRecOn generalizing init with
| _ minit ih =>
rw [_root_.Lean.Loop.forIn_eq]
mvcgen [step, ih] with
| vc2 =>
mrename_i h
mcases h with mb', hmeasure, hmb', h
mspec Triple.of_entails_wp (ih mb' hmb')
end
end Std.Do

View File

@@ -10,6 +10,7 @@ public import Std.Do.Triple.Basic
public import Init.Data.Range.Polymorphic.Iterators
import Init.Data.Range.Polymorphic
public import Init.Data.Slice.Array
public import Init.While
-- This public import is a workaround for #10652.
-- Without it, adding the `spec` attribute for `instMonadLiftTOfMonadLift` will fail.

View File

@@ -8,6 +8,7 @@ module
prelude
public import Std.Tactic.Do.ProofMode
public import Std.Tactic.Do.Syntax
public import Std.Do.Triple.RepeatSpec
@[expose] public section

View File

@@ -0,0 +1,126 @@
module
import Std
set_option mvcgen.warning false
open Std.Do
/-!
# Tests for `Loop`-based `repeat`/`while` loops with `extrinsicFix`
These tests verify that the `Loop.forIn` implementation using `extrinsicFix` and its
verification infrastructure work correctly with `MonadTail`.
-/
/-- `sqrt n` computes the integer square root of `n` using a `while` loop. -/
def sqrt (n : Nat) : Id Nat := do
if n = 0 then
return 0
let mut i := 0
while i * i n
do
i := i + 1
return i - 1
/-- The `sqrt` function returns the correct integer square root. -/
theorem sqrt_correct :
True sqrt n res => res * res n n < (res + 1) * (res+1) := by
mvcgen [sqrt]
invariants
| inv1 => fun i => ULift.up ((n + 2) - i)
| inv2 => (done, i) => j, j < i j * j n done = true n < i * i
with (try grind)
| vc2 r _ hsqr _ _ =>
have : r n := Nat.le_trans (Nat.le_mul_self r) hsqr
grind
| vc5 res h =>
have : res - 1 < res := by grind
grind
-- Verify sqrt computes correctly
#guard Id.run (sqrt 0) == 0
#guard Id.run (sqrt 1) == 1
#guard Id.run (sqrt 4) == 2
#guard Id.run (sqrt 8) == 2
#guard Id.run (sqrt 9) == 3
#guard Id.run (sqrt 15) == 3
#guard Id.run (sqrt 16) == 4
#guard Id.run (sqrt 100) == 10
/-- `sqrtState n` is the same as `sqrt` but uses `StateT`. -/
def sqrtState (n : Nat) : StateT Nat Id Nat := do
if n = 0 then
return 0
let mut i := 0
while i * i n
do
i := i + 1
return i - 1
/-- The `sqrtState` function returns the correct integer square root. -/
theorem sqrtState_correct :
True sqrtState n res => res * res n n < (res + 1) * (res+1) := by
mvcgen [sqrtState]
invariants
| inv1 => fun i _ => ULift.up ((n + 2) - i)
| inv2 => (done, i) => j, j < i j * j n done = true n < i * i
with (try grind)
| vc2 r _ hsqr _ _ _ =>
have : r n := Nat.le_trans (Nat.le_mul_self r) hsqr
grind
| vc5 res h _ =>
have : res - 1 < res := by grind
grind
/-- A loop that only terminates when the initial value satisfies `i ≤ x`. -/
def loopWithTerminationPrecond (x : Nat) : Id Nat := do
let mut i := 0
while i x do
i := i + 1
return i
example : True loopWithTerminationPrecond x r => r = x := by
mvcgen [loopWithTerminationPrecond] invariants
| inv1 => fun i => ULift.up (x - i)
| inv2 => (done, i) => i x done = true i = x
with grind
/-- A loop that only terminates when the initial *state* satisfies some invariant. -/
def loopWithStatefulTerminationPrecond (x : Nat) : StateM Nat Nat := do
set 0
while ( get) x do
modify fun i => i + 1
get
example : True loopWithStatefulTerminationPrecond x r => r = x := by
mvcgen [loopWithStatefulTerminationPrecond] invariants
| inv1 => fun _ s => ULift.up (x - s)
| inv2 => (done, _) s => s x (done = true s = x)
with (try grind)
/-- A loop that does not terminate for all inputs. -/
def possiblyDivergentLoop (x : Nat) : Id Nat := do
let mut x := x
while x 20 do
x := x + 1
return x
example : x 20 possiblyDivergentLoop x r => r = 20 := by
mvcgen [possiblyDivergentLoop] invariants
| inv1 => fun i => ULift.up (20 - i)
| inv2 => (done, i) => i 20 done = true i = 20
with grind
def terminatesSometimes (n : Nat) (p : Nat Bool) : Option Nat := do
let mut n := n
while !p n do
n := n + 2
return n
example (n m : Nat) (h : n m) (heven : n % 2 = 0) (hmeven : m % 2 = 0) (h : p m) :
True terminatesSometimes n p r => r % 2 = 0 := by
mvcgen [terminatesSometimes] invariants
| inv1 => fun i => ULift.up (m + 1 - i)
| inv2 => (done, i) => i % 2 = 0 i m done = true p i
with grind