Skip to content

Commit 4fb7ea2

Browse files
committed
feat: add Triple.observe
1 parent aa2317a commit 4fb7ea2

2 files changed

Lines changed: 97 additions & 0 deletions

File tree

src/Std/Do/Triple/Basic.lean

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,21 @@ for `Q₁`, then `mp x h₁ h₂` is a proof for `Q₂` about `x`.
110110
theorem mp [WP m ps] (x : m α) (h₁ : Triple x P₁ Q₁) (h₂ : Triple x P₂ (Q₁ →ₚ Q₂)) : Triple x spred(P₁ ∧ P₂) (Q₁ ∧ₚ Q₂) :=
111111
Triple.iff.mpr <| SPred.and_mono (Triple.iff.mp h₁) (Triple.iff.mp h₂) |>.trans ((wp x).conjunctive Q₁ (Q₁ →ₚ Q₂)).mpr |>.trans ((wp x).mono _ _ PostCond.and_imp)
112112

113+
/--
114+
Observes a fact `Q` about the state by running a stateless program `obs`, then carries `Q` into the
115+
proof of `prog`. A triple for `prog` follows from a triple for `obs` that assumes the postcondition
116+
`Q` of the specification `h` and establishes the goal `wp⟦prog⟧ Post`. This requires `obs` to be
117+
*stateless*: the premise `hp` states that its successful runs leave the state unchanged.
118+
-/
119+
theorem observe [WP m ps] {α β : Type u} {obs : m α} {prog : m β}
120+
{Pre : Assertion ps} {Q : PostCond α ps} {Post : PostCond β ps}
121+
(hp : ∀ C : Assertion ps, wp⟦obs⟧ (PostCond.noThrow fun _ => C) ⊢ₛ C)
122+
(h : Triple obs Pre Q)
123+
(hgoal : Triple obs Pre (Q →ₚ PostCond.noThrow fun _ => wp⟦prog⟧ Post)) :
124+
Triple prog Pre Post :=
125+
Triple.of_entails_wp <|
126+
(Triple.entails_wp_of_pre_post (Triple.mp obs h hgoal) SPred.and_self.mpr
127+
(PostCond.entails.mk (fun _ => SPred.and_elim_r) (ExceptConds.and_elim_right _ _))).trans
128+
(hp (wp⟦prog⟧ Post))
129+
113130
end Triple

tests/elab/doLogicTests.lean

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,3 +829,83 @@ theorem foldM_eq_sum (xs : Array Nat) {m ps} [Monad m] [LawfulMonad m]
829829
mvcgen
830830
case inv1 => exact ⇓⟨cur, n⟩ => ⌜n = cur.prefix.sum⌝
831831
all_goals grind
832+
833+
/-! ### The partial correctness trick
834+
835+
Specifications are written in the object language as boolean monadic predicates. A triple
836+
hypothesis is consumed by observing its program via `Triple.observe`
837+
and stepping through it with `mvcgen`. -/
838+
839+
namespace PartialCorrectnessTrick
840+
841+
inductive RustError where
842+
| rustPanic
843+
| integerOverflow
844+
845+
abbrev RustM := Except RustError
846+
847+
/-- A boolean monadic predicate holds when it returns `true` without throwing. -/
848+
abbrev RustM.holds (p : RustM Bool) : Prop := ⦃⌜True⌝⦄ p ⦃⇓ r => ⌜r⌝⦄
849+
850+
/-- Running an `Except` program and discarding its result preserves any goal. -/
851+
theorem wp_const (p : RustM α) (C : Assertion (.except RustError .pure)) :
852+
wp⟦p⟧ (⇓ _ => C) ⊢ₛ C := by
853+
cases p with
854+
| ok a => exact SPred.entails.refl _
855+
| error e => exact SPred.false_elim
856+
857+
namespace Unfoldable
858+
859+
def panicAdd (x y : Nat) : RustM Nat := do
860+
if (x + y) ≤ 100 then pure (x + y) else Except.error .integerOverflow
861+
862+
def pre (x : Nat) : RustM Bool := do
863+
if x ≤ 10 then
864+
pure ((← panicAdd x 10) == 15)
865+
else
866+
pure false
867+
868+
def f (x : Nat) : RustM Nat := do
869+
panicAdd x 5
870+
871+
def post (x : Nat) (res : Nat) : RustM Bool := do
872+
pure (res == 10)
873+
874+
example (x : Nat) :
875+
(pre x).holds → ⦃⌜True⌝⦄ f x ⦃⇓ res => ⌜(post x res).holds⌝⦄ := by
876+
intro h
877+
apply Triple.observe (wp_const (pre x)) h
878+
mvcgen [pre, panicAdd] <;> try grind
879+
intro hx
880+
mvcgen [f, post, panicAdd] <;> try grind
881+
mvcgen [post] <;> grind
882+
883+
end Unfoldable
884+
885+
namespace Opaque
886+
887+
opaque g : Nat → RustM Nat
888+
889+
def pre (x : Nat) : RustM Bool := do pure (x == (← g 0))
890+
891+
def f (x : Nat) : RustM Nat := pure (x + 1)
892+
893+
def post (x : Nat) (res : Nat) : RustM Bool := do pure (res - 1 == (← g 0))
894+
895+
/-- Partial-correctness self-specification: if `p` succeeds with `r`, then `p = .ok r`. -/
896+
theorem self_spec (p : RustM α) : ⦃⌜True⌝⦄ p ⦃⇓? r => ⌜p = .ok r⌝⦄ := by
897+
cases p with
898+
| ok a => exact Triple.pure a (by simp)
899+
| error e => apply Triple.of_entails_wp; intro _; exact True.intro
900+
901+
example (x : Nat) :
902+
(pre x).holds → ⦃⌜True⌝⦄ f x ⦃⇓ res => ⌜(post x res).holds⌝⦄ := by
903+
intro h
904+
apply Triple.observe (wp_const (pre x)) h
905+
have hg := self_spec (g 0)
906+
mvcgen [pre, hg, f] <;> try grind
907+
simp
908+
909+
end Opaque
910+
911+
end PartialCorrectnessTrick

0 commit comments

Comments
 (0)