Skip to content

Commit 772678d

Browse files
committed
Add modular relational algebra and cut over relational logic
1 parent cfc5432 commit 772678d

File tree

4 files changed

+519
-45
lines changed

4 files changed

+519
-45
lines changed

ToMathlib.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import ToMathlib.Control.Monad.Free
1414
import ToMathlib.Control.Monad.FreeCont
1515
import ToMathlib.Control.Monad.Hom
1616
import ToMathlib.Control.Monad.Ordered
17+
import ToMathlib.Control.Monad.RelationalAlgebra
1718
import ToMathlib.Control.Monad.Relation
1819
import ToMathlib.Control.Monad.Relative
1920
import ToMathlib.Control.Monad.Transformer
Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
/-
2+
Copyright (c) 2026 Quang Dao. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Quang Dao
5+
-/
6+
7+
import Mathlib.Order.CompleteLattice.Basic
8+
9+
/-!
10+
# Relational monad algebras
11+
12+
This file introduces a two-monad relational analogue of `MAlgOrdered`:
13+
14+
* `MAlgRelOrdered m₁ m₂ l` with a relational weakest-precondition operator `rwp`.
15+
* Generic relational triple rules (`pure`, `consequence`, `bind`).
16+
* Side-lifting instances for heterogeneous stacks (`StateT`, `OptionT`, `ExceptT`).
17+
18+
Attribution:
19+
- Loom repository: https://github.com/verse-lab/loom
20+
- POPL 2026 paper: *Foundational Multi-Modal Program Verifiers*,
21+
Vladimir Gladshtein, George Pîrlea, Qiyuan Zhao, Vitaly Kurin, Ilya Sergey.
22+
DOI: https://doi.org/10.1145/3776719
23+
-/
24+
25+
universe u v₁ v₂ v₃ v₄
26+
27+
/-- Ordered relational monad algebra between two monads. -/
28+
class MAlgRelOrdered (m₁ : Type u → Type v₁) (m₂ : Type u → Type v₂) (l : Type u)
29+
[Monad m₁] [Monad m₂] [Preorder l] where
30+
/-- Relational weakest precondition. -/
31+
rwp : {α β : Type u} → m₁ α → m₂ β → (α → β → l) → l
32+
/-- Pure rule for the relational weakest precondition. -/
33+
rwp_pure {α β : Type u} (a : α) (b : β) (post : α → β → l) :
34+
rwp (pure a) (pure b) post = post a b
35+
/-- Monotonicity in the relational postcondition. -/
36+
rwp_mono {α β : Type u} {x : m₁ α} {y : m₂ β} {post post' : α → β → l}
37+
(hpost : ∀ a b, post a b ≤ post' a b) :
38+
rwp x y post ≤ rwp x y post'
39+
/-- Sequential composition rule for relational WPs. -/
40+
rwp_bind_le {α β γ δ : Type u}
41+
(x : m₁ α) (y : m₂ β) (f : α → m₁ γ) (g : β → m₂ δ) (post : γ → δ → l) :
42+
rwp x y (fun a b => rwp (f a) (g b) post) ≤ rwp (x >>= f) (y >>= g) post
43+
44+
namespace MAlgRelOrdered
45+
46+
variable {m₁ : Type u → Type v₁} {m₂ : Type u → Type v₂} {l : Type u}
47+
variable [Monad m₁] [Monad m₂] [Preorder l] [MAlgRelOrdered m₁ m₂ l]
48+
variable {α β γ δ : Type u}
49+
50+
/-- Relational weakest precondition induced by `MAlgRelOrdered`. -/
51+
abbrev RelWP (x : m₁ α) (y : m₂ β) (post : α → β → l) : l :=
52+
MAlgRelOrdered.rwp x y post
53+
54+
/-- Relational Hoare-style triple. -/
55+
def Triple (pre : l) (x : m₁ α) (y : m₂ β) (post : α → β → l) : Prop :=
56+
pre ≤ RelWP x y post
57+
58+
@[simp]
59+
theorem relWP_pure [LawfulMonad m₁] [LawfulMonad m₂] (a : α) (b : β) (post : α → β → l) :
60+
RelWP (pure a : m₁ α) (pure b : m₂ β) post = post a b :=
61+
MAlgRelOrdered.rwp_pure a b post
62+
63+
theorem relWP_mono (x : m₁ α) (y : m₂ β) {post post' : α → β → l}
64+
(hpost : ∀ a b, post a b ≤ post' a b) :
65+
RelWP x y post ≤ RelWP x y post' :=
66+
MAlgRelOrdered.rwp_mono hpost
67+
68+
theorem relWP_bind_le (x : m₁ α) (y : m₂ β) (f : α → m₁ γ) (g : β → m₂ δ)
69+
(post : γ → δ → l) :
70+
RelWP x y (fun a b => RelWP (f a) (g b) post) ≤ RelWP (x >>= f) (y >>= g) post :=
71+
MAlgRelOrdered.rwp_bind_le x y f g post
72+
73+
theorem triple_conseq {pre pre' : l} {x : m₁ α} {y : m₂ β} {post post' : α → β → l}
74+
(hpre : pre' ≤ pre) (hpost : ∀ a b, post a b ≤ post' a b) :
75+
Triple pre x y post → Triple pre' x y post' := by
76+
intro hxy
77+
exact le_trans hpre (le_trans hxy (relWP_mono x y hpost))
78+
79+
theorem triple_pure [LawfulMonad m₁] [LawfulMonad m₂]
80+
{pre : l} {a : α} {b : β} {post : α → β → l}
81+
(hpre : pre ≤ post a b) :
82+
Triple pre (pure a : m₁ α) (pure b : m₂ β) post := by
83+
simpa [Triple, relWP_pure] using hpre
84+
85+
theorem triple_bind {pre : l} {x : m₁ α} {y : m₂ β}
86+
{f : α → m₁ γ} {g : β → m₂ δ}
87+
{cut : α → β → l} {post : γ → δ → l}
88+
(hxy : Triple pre x y cut)
89+
(hfg : ∀ a b, Triple (cut a b) (f a) (g b) post) :
90+
Triple pre (x >>= f) (y >>= g) post := by
91+
have hcut : pre ≤ RelWP x y (fun a b => RelWP (f a) (g b) post) := by
92+
exact le_trans hxy (relWP_mono x y hfg)
93+
exact le_trans hcut (relWP_bind_le x y f g post)
94+
95+
/-- Mapping on the left program is monotone for relational WP. -/
96+
theorem relWP_map_left [LawfulMonad m₁] [LawfulMonad m₂]
97+
(f : α → γ) (x : m₁ α) (y : m₂ β) (post : γ → β → l) :
98+
RelWP x y (fun a b => post (f a) b) ≤ RelWP (f <$> x) y post := by
99+
have hbind := relWP_bind_le x y (fun a => pure (f a)) (fun b => pure b) post
100+
simpa [Functor.map, bind_pure_comp, relWP_pure] using hbind
101+
102+
/-- Mapping on the right program is monotone for relational WP. -/
103+
theorem relWP_map_right [LawfulMonad m₁] [LawfulMonad m₂]
104+
(g : β → δ) (x : m₁ α) (y : m₂ β) (post : α → δ → l) :
105+
RelWP x y (fun a b => post a (g b)) ≤ RelWP x (g <$> y) post := by
106+
have hbind := relWP_bind_le x y (fun a => pure a) (fun b => pure (g b)) post
107+
simpa [Functor.map, bind_pure_comp, relWP_pure] using hbind
108+
109+
end MAlgRelOrdered
110+
111+
namespace MAlgRelOrdered
112+
113+
section Instances
114+
115+
variable {m₁ : Type u → Type v₁} {m₂ : Type u → Type v₂} {l : Type u}
116+
variable [Monad m₁] [Monad m₂] [LawfulMonad m₁] [LawfulMonad m₂] [Preorder l]
117+
variable [MAlgRelOrdered m₁ m₂ l]
118+
119+
/-- Left `StateT` lift for heterogeneous relational algebras. -/
120+
noncomputable instance instStateTLeft (σ : Type u) :
121+
MAlgRelOrdered (StateT σ m₁) m₂ (σ → l) where
122+
rwp x y post := fun s =>
123+
MAlgRelOrdered.rwp (x.run s) y (fun xs b => post xs.1 b xs.2)
124+
rwp_pure a b post := by
125+
funext s
126+
simp [StateT.run_pure]
127+
rwp_mono hpost := by
128+
intro s
129+
exact MAlgRelOrdered.rwp_mono (m₁ := m₁) (m₂ := m₂) (l := l) (fun xs b => hpost xs.1 b xs.2)
130+
rwp_bind_le x y f g post := by
131+
intro s
132+
simpa [StateT.run_bind] using
133+
(MAlgRelOrdered.rwp_bind_le (m₁ := m₁) (m₂ := m₂) (l := l)
134+
(x := x.run s) (y := y)
135+
(f := fun xs => (f xs.1).run xs.2) (g := g)
136+
(post := fun zs d => post zs.1 d zs.2))
137+
138+
/-- Right `StateT` lift for heterogeneous relational algebras. -/
139+
noncomputable instance instStateTRight (σ : Type u) :
140+
MAlgRelOrdered m₁ (StateT σ m₂) (σ → l) where
141+
rwp x y post := fun s =>
142+
MAlgRelOrdered.rwp x (y.run s) (fun a ys => post a ys.1 ys.2)
143+
rwp_pure a b post := by
144+
funext s
145+
simp [StateT.run_pure]
146+
rwp_mono hpost := by
147+
intro s
148+
exact MAlgRelOrdered.rwp_mono (m₁ := m₁) (m₂ := m₂) (l := l) (fun a ys => hpost a ys.1 ys.2)
149+
rwp_bind_le x y f g post := by
150+
intro s
151+
simpa [StateT.run_bind] using
152+
(MAlgRelOrdered.rwp_bind_le (m₁ := m₁) (m₂ := m₂) (l := l)
153+
(x := x) (y := y.run s)
154+
(f := f) (g := fun ys => (g ys.1).run ys.2)
155+
(post := fun c td => post c td.1 td.2))
156+
157+
end Instances
158+
159+
section FailureInstances
160+
161+
variable {m₁ : Type u → Type v₁} {m₂ : Type u → Type v₂} {l : Type u}
162+
variable [Monad m₁] [Monad m₂] [LawfulMonad m₁] [LawfulMonad m₂] [Preorder l] [OrderBot l]
163+
variable [MAlgRelOrdered m₁ m₂ l]
164+
165+
/-- Right `OptionT` lift (interpreting `none` as `⊥`). -/
166+
noncomputable instance instOptionTRight :
167+
MAlgRelOrdered m₁ (OptionT m₂) l where
168+
rwp x y post :=
169+
MAlgRelOrdered.rwp x y.run (fun a ob =>
170+
match ob with
171+
| none => ⊥
172+
| some b => post a b)
173+
rwp_pure a b post := by
174+
simp
175+
rwp_mono hpost := by
176+
exact MAlgRelOrdered.rwp_mono (m₁ := m₁) (m₂ := m₂) (l := l) (fun a ob => by
177+
cases ob with
178+
| none => exact le_rfl
179+
| some b => simpa using hpost a b)
180+
rwp_bind_le {α β γ δ} x y f g post := by
181+
let collapse : γ → Option δ → l := fun c od =>
182+
match od with
183+
| none => ⊥
184+
| some d => post c d
185+
let gRun : Option β → m₂ (Option δ) := fun ob =>
186+
Option.elim ob (pure none) (fun b => (g b).run)
187+
have hmono :
188+
MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) x y.run
189+
(fun a ob =>
190+
match ob with
191+
| none => ⊥
192+
| some b => MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) (f a) (g b).run collapse)
193+
194+
MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) x y.run
195+
(fun a ob => MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) (f a) (gRun ob) collapse) := by
196+
apply MAlgRelOrdered.rwp_mono (m₁ := m₁) (m₂ := m₂) (l := l)
197+
intro a ob
198+
cases ob with
199+
| none =>
200+
simp [gRun, collapse]
201+
| some b =>
202+
simp [gRun]
203+
exact le_trans hmono <|
204+
by
205+
simpa [OptionT.run_bind, Option.elimM, gRun, collapse] using
206+
(MAlgRelOrdered.rwp_bind_le (m₁ := m₁) (m₂ := m₂) (l := l)
207+
x y.run f gRun collapse)
208+
209+
/-- Left `OptionT` lift (interpreting `none` as `⊥`). -/
210+
noncomputable instance instOptionTLeft :
211+
MAlgRelOrdered (OptionT m₁) m₂ l where
212+
rwp x y post :=
213+
MAlgRelOrdered.rwp x.run y (fun oa b =>
214+
match oa with
215+
| none => ⊥
216+
| some a => post a b)
217+
rwp_pure a b post := by
218+
simp
219+
rwp_mono hpost := by
220+
exact MAlgRelOrdered.rwp_mono (m₁ := m₁) (m₂ := m₂) (l := l) (fun oa b => by
221+
cases oa with
222+
| none => exact le_rfl
223+
| some a => simpa using hpost a b)
224+
rwp_bind_le {α β γ δ} x y f g post := by
225+
let collapse : Option γ → δ → l := fun oa b =>
226+
match oa with
227+
| none => ⊥
228+
| some a => post a b
229+
let fRun : Option α → m₁ (Option γ) := fun oa =>
230+
Option.elim oa (pure none) (fun a => (f a).run)
231+
have hmono :
232+
MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) x.run y
233+
(fun oa b =>
234+
match oa with
235+
| none => ⊥
236+
| some a => MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) (f a).run (g b) collapse)
237+
238+
MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) x.run y
239+
(fun oa b => MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) (fRun oa) (g b) collapse) := by
240+
apply MAlgRelOrdered.rwp_mono (m₁ := m₁) (m₂ := m₂) (l := l)
241+
intro oa b
242+
cases oa with
243+
| none =>
244+
simp [fRun, collapse]
245+
| some a =>
246+
simp [fRun]
247+
have hmono' :
248+
MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) x.run y
249+
(fun oa b =>
250+
match oa with
251+
| none => ⊥
252+
| some a => MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) (f a).run (g b) collapse)
253+
254+
MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) x.run y
255+
(fun oa b => MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) (fRun oa) (g b) collapse) := by
256+
simpa [collapse] using hmono
257+
exact le_trans hmono' <|
258+
by
259+
simpa [OptionT.run_bind, Option.elimM, fRun, collapse] using
260+
(MAlgRelOrdered.rwp_bind_le (m₁ := m₁) (m₂ := m₂) (l := l)
261+
x.run y fRun g collapse)
262+
263+
/-- Right `ExceptT` lift (interpreting exceptions as `⊥`). -/
264+
noncomputable instance instExceptTRight (ε : Type u) :
265+
MAlgRelOrdered m₁ (ExceptT ε m₂) l where
266+
rwp x y post :=
267+
MAlgRelOrdered.rwp x y.run (fun a eb =>
268+
match eb with
269+
| Except.error _ => ⊥
270+
| Except.ok b => post a b)
271+
rwp_pure a b post := by
272+
simp
273+
rwp_mono hpost := by
274+
exact MAlgRelOrdered.rwp_mono (m₁ := m₁) (m₂ := m₂) (l := l) (fun a eb => by
275+
cases eb with
276+
| error e => exact le_rfl
277+
| ok b => simpa using hpost a b)
278+
rwp_bind_le {α β γ δ} x y f g post := by
279+
let collapse : γ → Except ε δ → l := fun c ed =>
280+
match ed with
281+
| Except.error _ => ⊥
282+
| Except.ok d => post c d
283+
let gRun : Except ε β → m₂ (Except ε δ) := fun eb =>
284+
match eb with
285+
| Except.ok b => (g b).run
286+
| Except.error e => pure (Except.error e)
287+
have hmono :
288+
MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) x y.run
289+
(fun a eb =>
290+
match eb with
291+
| Except.error _ => ⊥
292+
| Except.ok b =>
293+
MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) (f a) (g b).run collapse)
294+
295+
MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) x y.run
296+
(fun a eb => MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) (f a) (gRun eb) collapse) := by
297+
apply MAlgRelOrdered.rwp_mono (m₁ := m₁) (m₂ := m₂) (l := l)
298+
intro a eb
299+
cases eb with
300+
| error e =>
301+
simp [gRun, collapse]
302+
| ok b =>
303+
simp [gRun]
304+
exact le_trans hmono <|
305+
by
306+
simpa [ExceptT.run_bind, gRun, collapse] using
307+
(MAlgRelOrdered.rwp_bind_le (m₁ := m₁) (m₂ := m₂) (l := l)
308+
x y.run f gRun collapse)
309+
310+
/-- Left `ExceptT` lift (interpreting exceptions as `⊥`). -/
311+
noncomputable instance instExceptTLeft (ε : Type u) :
312+
MAlgRelOrdered (ExceptT ε m₁) m₂ l where
313+
rwp x y post :=
314+
MAlgRelOrdered.rwp x.run y (fun ea b =>
315+
match ea with
316+
| Except.error _ => ⊥
317+
| Except.ok a => post a b)
318+
rwp_pure a b post := by
319+
simp
320+
rwp_mono hpost := by
321+
exact MAlgRelOrdered.rwp_mono (m₁ := m₁) (m₂ := m₂) (l := l) (fun ea b => by
322+
cases ea with
323+
| error e => exact le_rfl
324+
| ok a => simpa using hpost a b)
325+
rwp_bind_le {α β γ δ} x y f g post := by
326+
let collapse : Except ε γ → δ → l := fun ec d =>
327+
match ec with
328+
| Except.error _ => ⊥
329+
| Except.ok c => post c d
330+
let fRun : Except ε α → m₁ (Except ε γ) := fun ea =>
331+
match ea with
332+
| Except.ok a => (f a).run
333+
| Except.error e => pure (Except.error e)
334+
have hmono :
335+
MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) x.run y
336+
(fun ea b =>
337+
match ea with
338+
| Except.error _ => ⊥
339+
| Except.ok a =>
340+
MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) (f a).run (g b) collapse)
341+
342+
MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) x.run y
343+
(fun ea b => MAlgRelOrdered.rwp (m₁ := m₁) (m₂ := m₂) (l := l) (fRun ea) (g b) collapse) := by
344+
apply MAlgRelOrdered.rwp_mono (m₁ := m₁) (m₂ := m₂) (l := l)
345+
intro ea b
346+
cases ea with
347+
| error e =>
348+
simp [fRun, collapse]
349+
| ok a =>
350+
simp [fRun]
351+
exact le_trans hmono <|
352+
by
353+
simpa [ExceptT.run_bind, fRun, collapse] using
354+
(MAlgRelOrdered.rwp_bind_le (m₁ := m₁) (m₂ := m₂) (l := l)
355+
x.run y fRun g collapse)
356+
357+
end FailureInstances
358+
359+
end MAlgRelOrdered

0 commit comments

Comments
 (0)