|
| 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