|
| 1 | +/- |
| 2 | + Metatheory / AlgebraicEffectsDeep.lean |
| 3 | +
|
| 4 | + Algebraic effects with deep and shallow handler semantics. |
| 5 | + — Effect signatures as finite sets of operation names |
| 6 | + — Free monad (computation trees) over effect signatures |
| 7 | + — Deep handlers (recursive) vs shallow handlers (one-shot) |
| 8 | + — Effect forwarding and tunneling |
| 9 | + — Monad laws for the free monad |
| 10 | + — Handler composition and fusion |
| 11 | +
|
| 12 | + All proofs are sorry-free, no sorry, no admit. |
| 13 | +-/ |
| 14 | + |
| 15 | +namespace AlgebraicEffectsDeep |
| 16 | + |
| 17 | +-- ============================================================ |
| 18 | +-- §1 Effect Signatures (Nat-indexed operations) |
| 19 | +-- ============================================================ |
| 20 | + |
| 21 | +/-- An operation name. -/ |
| 22 | +abbrev OpName := Nat |
| 23 | + |
| 24 | +/-- An effect signature is a set of operation names. -/ |
| 25 | +abbrev Sig := List OpName |
| 26 | + |
| 27 | +-- ============================================================ |
| 28 | +-- §2 Free Monad (Computation Trees) |
| 29 | +-- ============================================================ |
| 30 | + |
| 31 | +/-- Free monad / computation tree over a signature. |
| 32 | + `op` carries the operation name, a Nat parameter, and a |
| 33 | + continuation expecting a Nat result. -/ |
| 34 | +inductive Free (sig : Sig) (A : Type) where |
| 35 | + | pure : A → Free sig A |
| 36 | + | op : (name : OpName) → name ∈ sig → Nat → (Nat → Free sig A) → Free sig A |
| 37 | + |
| 38 | +variable {sig sig₁ sig₂ : Sig} {A B C D : Type} |
| 39 | + |
| 40 | +/-- Monadic bind. -/ |
| 41 | +def Free.bind (m : Free sig A) (f : A → Free sig B) : Free sig B := |
| 42 | + match m with |
| 43 | + | .pure a => f a |
| 44 | + | .op n mem p k => .op n mem p (fun r => (k r).bind f) |
| 45 | + |
| 46 | +/-- Monadic map. -/ |
| 47 | +def Free.map (g : A → B) (m : Free sig A) : Free sig B := |
| 48 | + m.bind (fun a => .pure (g a)) |
| 49 | + |
| 50 | +-- ============================================================ |
| 51 | +-- §3 Monad Laws |
| 52 | +-- ============================================================ |
| 53 | + |
| 54 | +@[simp] theorem Free.bind_pure_left (a : A) (f : A → Free sig B) : |
| 55 | + (Free.pure a).bind f = f a := rfl |
| 56 | + |
| 57 | +theorem Free.bind_assoc (m : Free sig A) (f : A → Free sig B) (g : B → Free sig C) : |
| 58 | + (m.bind f).bind g = m.bind (fun a => (f a).bind g) := by |
| 59 | + induction m with |
| 60 | + | pure a => simp [Free.bind] |
| 61 | + | op n mem p k ih => simp [Free.bind]; funext r; exact ih r |
| 62 | + |
| 63 | +theorem Free.bind_pure_right (m : Free sig A) : |
| 64 | + m.bind Free.pure = m := by |
| 65 | + induction m with |
| 66 | + | pure a => rfl |
| 67 | + | op n mem p k ih => simp [Free.bind]; funext r; exact ih r |
| 68 | + |
| 69 | +theorem Free.map_pure (g : A → B) (a : A) : |
| 70 | + Free.map g (Free.pure a : Free sig A) = Free.pure (g a) := rfl |
| 71 | + |
| 72 | +theorem Free.map_map (g₁ : A → B) (g₂ : B → C) (m : Free sig A) : |
| 73 | + Free.map g₂ (Free.map g₁ m) = Free.map (g₂ ∘ g₁) m := by |
| 74 | + simp [Free.map, Free.bind_assoc, Free.bind] |
| 75 | + |
| 76 | +theorem Free.map_id (m : Free sig A) : |
| 77 | + Free.map id m = m := by |
| 78 | + simp [Free.map, Free.bind_pure_right] |
| 79 | + |
| 80 | +theorem Free.map_bind (g : B → C) (m : Free sig A) (f : A → Free sig B) : |
| 81 | + Free.map g (m.bind f) = m.bind (fun a => Free.map g (f a)) := by |
| 82 | + simp [Free.map, Free.bind_assoc] |
| 83 | + |
| 84 | +-- ============================================================ |
| 85 | +-- §4 Deep Handlers |
| 86 | +-- ============================================================ |
| 87 | + |
| 88 | +/-- A deep handler for a named operation `opN` in signature `sig`. |
| 89 | + retClause: what to do with the final value. |
| 90 | + opClause: given the parameter and a resumption, produce a result. -/ |
| 91 | +structure DeepHandler (opN : OpName) (sig : Sig) (A B : Type) where |
| 92 | + retClause : A → Free sig B |
| 93 | + opClause : Nat → (Nat → Free sig B) → Free sig B |
| 94 | + |
| 95 | +/-- Apply a deep handler that strips operation `opN` from `opN :: sig`. -/ |
| 96 | +def handleDeep (h : DeepHandler opN sig A B) : |
| 97 | + Free (opN :: sig) A → Free sig B |
| 98 | + | .pure a => h.retClause a |
| 99 | + | .op n mem p k => |
| 100 | + if heq : n = opN then |
| 101 | + h.opClause p (fun r => handleDeep h (k r)) |
| 102 | + else |
| 103 | + have : n ∈ sig := by |
| 104 | + cases mem with |
| 105 | + | head => exact absurd rfl heq |
| 106 | + | tail _ htl => exact htl |
| 107 | + .op n this p (fun r => handleDeep h (k r)) |
| 108 | + |
| 109 | +theorem handleDeep_pure (h : DeepHandler opN sig A B) (a : A) : |
| 110 | + handleDeep h (.pure a) = h.retClause a := rfl |
| 111 | + |
| 112 | +-- ============================================================ |
| 113 | +-- §5 Shallow Handlers |
| 114 | +-- ============================================================ |
| 115 | + |
| 116 | +/-- A shallow handler: the continuation retains the original signature. -/ |
| 117 | +structure ShallowHandler (opN : OpName) (sig : Sig) (A B : Type) where |
| 118 | + retClause : A → Free sig B |
| 119 | + opClause : Nat → (Nat → Free (opN :: sig) A) → Free sig B |
| 120 | + |
| 121 | +/-- Apply a shallow handler. -/ |
| 122 | +def handleShallow (h : ShallowHandler opN sig A B) : |
| 123 | + Free (opN :: sig) A → Free sig B |
| 124 | + | .pure a => h.retClause a |
| 125 | + | .op n mem p k => |
| 126 | + if heq : n = opN then |
| 127 | + h.opClause p k |
| 128 | + else |
| 129 | + have : n ∈ sig := by |
| 130 | + cases mem with |
| 131 | + | head => exact absurd rfl heq |
| 132 | + | tail _ htl => exact htl |
| 133 | + .op n this p (fun r => handleShallow h (k r)) |
| 134 | + |
| 135 | +theorem handleShallow_pure (h : ShallowHandler opN sig A B) (a : A) : |
| 136 | + handleShallow h (.pure a) = h.retClause a := rfl |
| 137 | + |
| 138 | +-- ============================================================ |
| 139 | +-- §6 Effect Forwarding / Weakening |
| 140 | +-- ============================================================ |
| 141 | + |
| 142 | +/-- Weaken: embed a computation into a larger signature. -/ |
| 143 | +def Free.weaken (m : Free sig A) (h : ∀ n, n ∈ sig → n ∈ sig') : |
| 144 | + Free sig' A := |
| 145 | + match m with |
| 146 | + | .pure a => .pure a |
| 147 | + | .op n mem p k => .op n (h n mem) p (fun r => (k r).weaken h) |
| 148 | + |
| 149 | +theorem Free.weaken_pure (a : A) (h : ∀ n, n ∈ sig → n ∈ sig') : |
| 150 | + (Free.pure a : Free sig A).weaken h = Free.pure a := rfl |
| 151 | + |
| 152 | +theorem Free.weaken_bind (m : Free sig A) (f : A → Free sig B) |
| 153 | + (h : ∀ n, n ∈ sig → n ∈ sig') : |
| 154 | + (m.bind f).weaken h = (m.weaken h).bind (fun a => (f a).weaken h) := by |
| 155 | + induction m with |
| 156 | + | pure a => simp [Free.bind, Free.weaken] |
| 157 | + | op n mem p k ih => |
| 158 | + simp [Free.bind, Free.weaken] |
| 159 | + funext r; exact ih r |
| 160 | + |
| 161 | +-- ============================================================ |
| 162 | +-- §7 Inject into cons-signature |
| 163 | +-- ============================================================ |
| 164 | + |
| 165 | +theorem List.mem_cons_self_or (n : OpName) (sig : Sig) : |
| 166 | + ∀ m, m ∈ sig → m ∈ (n :: sig) := |
| 167 | + fun _ hm => List.mem_cons_of_mem n hm |
| 168 | + |
| 169 | +/-- Inject into opN :: sig from sig. -/ |
| 170 | +def Free.inject (m : Free sig A) : Free (opN :: sig) A := |
| 171 | + m.weaken (List.mem_cons_self_or opN sig) |
| 172 | + |
| 173 | +theorem Free.inject_pure (a : A) : |
| 174 | + (Free.pure a : Free sig A).inject = (Free.pure a : Free (opN :: sig) A) := rfl |
| 175 | + |
| 176 | +theorem Free.inject_bind (m : Free sig A) (f : A → Free sig B) : |
| 177 | + (m.bind f).inject = (m.inject : Free (opN :: sig) A).bind |
| 178 | + (fun a => (f a).inject) := by |
| 179 | + exact Free.weaken_bind m f _ |
| 180 | + |
| 181 | +-- ============================================================ |
| 182 | +-- §8 Pure Computation Predicate |
| 183 | +-- ============================================================ |
| 184 | + |
| 185 | +def Free.isPure : Free sig A → Prop |
| 186 | + | .pure _ => True |
| 187 | + | .op _ _ _ _ => False |
| 188 | + |
| 189 | +theorem Free.isPure_pure (a : A) : (Free.pure a : Free sig A).isPure := trivial |
| 190 | + |
| 191 | +theorem Free.isPure_elim {m : Free sig A} (hm : m.isPure) : ∃ a, m = .pure a := by |
| 192 | + match m with |
| 193 | + | .pure a => exact ⟨a, rfl⟩ |
| 194 | + | .op _ _ _ _ => exact absurd hm (by simp [Free.isPure]) |
| 195 | + |
| 196 | +-- ============================================================ |
| 197 | +-- §9 Handler Return-Clause Composition |
| 198 | +-- ============================================================ |
| 199 | + |
| 200 | +def composeRet (f : A → Free sig B) (g : B → Free sig C) : A → Free sig C := |
| 201 | + fun a => (f a).bind g |
| 202 | + |
| 203 | +theorem composeRet_pure_left (g : A → Free sig B) : |
| 204 | + composeRet Free.pure g = g := by |
| 205 | + funext a; simp [composeRet, Free.bind] |
| 206 | + |
| 207 | +theorem composeRet_pure_right (f : A → Free sig B) : |
| 208 | + composeRet f Free.pure = f := by |
| 209 | + funext a; simp [composeRet, Free.bind_pure_right] |
| 210 | + |
| 211 | +theorem composeRet_assoc (f : A → Free sig B) (g : B → Free sig C) (h' : C → Free sig D) : |
| 212 | + composeRet (composeRet f g) h' = composeRet f (composeRet g h') := by |
| 213 | + funext a; unfold composeRet; rw [Free.bind_assoc] |
| 214 | + |
| 215 | +-- ============================================================ |
| 216 | +-- §10 Tunneling Handlers |
| 217 | +-- ============================================================ |
| 218 | + |
| 219 | +/-- Construct a tunneling handler: handle opN₁ while forwarding opN₂. -/ |
| 220 | +def tunnelHandler (opN₁ opN₂ : OpName) (sig : Sig) (A B : Type) |
| 221 | + (ret : A → Free (opN₂ :: sig) B) |
| 222 | + (clause : Nat → (Nat → Free (opN₂ :: sig) B) → Free (opN₂ :: sig) B) : |
| 223 | + DeepHandler opN₁ (opN₂ :: sig) A B := |
| 224 | + { retClause := ret, opClause := clause } |
| 225 | + |
| 226 | +theorem tunnelHandler_ret (opN₁ opN₂ : OpName) (sig : Sig) (A B : Type) |
| 227 | + (ret : A → Free (opN₂ :: sig) B) |
| 228 | + (clause : Nat → (Nat → Free (opN₂ :: sig) B) → Free (opN₂ :: sig) B) |
| 229 | + (a : A) : |
| 230 | + (tunnelHandler opN₁ opN₂ sig A B ret clause).retClause a = ret a := rfl |
| 231 | + |
| 232 | +-- ============================================================ |
| 233 | +-- §11 Parameterised Handlers |
| 234 | +-- ============================================================ |
| 235 | + |
| 236 | +/-- Parameterised handler threads a state through handling. -/ |
| 237 | +structure ParamHandler (opN : OpName) (sig : Sig) (St A B : Type) where |
| 238 | + retClause : St → A → Free sig B |
| 239 | + opClause : St → Nat → (St → Nat → Free sig B) → Free sig B |
| 240 | + |
| 241 | +/-- Apply a parameterised handler. -/ |
| 242 | +def handleParam (h : ParamHandler opN sig St A B) (st : St) : |
| 243 | + Free (opN :: sig) A → Free sig B |
| 244 | + | .pure a => h.retClause st a |
| 245 | + | .op n mem p k => |
| 246 | + if heq : n = opN then |
| 247 | + h.opClause st p (fun st' r => handleParam h st' (k r)) |
| 248 | + else |
| 249 | + have : n ∈ sig := by |
| 250 | + cases mem with |
| 251 | + | head => exact absurd rfl heq |
| 252 | + | tail _ htl => exact htl |
| 253 | + .op n this p (fun r => handleParam h st (k r)) |
| 254 | + |
| 255 | +theorem handleParam_pure (h : ParamHandler opN sig St A B) (st : St) (a : A) : |
| 256 | + handleParam h st (.pure a) = h.retClause st a := rfl |
| 257 | + |
| 258 | +-- ============================================================ |
| 259 | +-- §12 Operation Count |
| 260 | +-- ============================================================ |
| 261 | + |
| 262 | +noncomputable def Free.opCount : Free sig A → Nat |
| 263 | + | .pure _ => 0 |
| 264 | + | .op _ _ _ _ => 1 |
| 265 | + |
| 266 | +theorem Free.opCount_pure (a : A) : (Free.pure a : Free sig A).opCount = 0 := rfl |
| 267 | + |
| 268 | +-- ============================================================ |
| 269 | +-- §13 Deep handler on injected (forwarded) trees |
| 270 | +-- ============================================================ |
| 271 | + |
| 272 | +theorem handleDeep_inject_pure (h : DeepHandler opN sig A B) (a : A) : |
| 273 | + handleDeep h (Free.inject (.pure a)) = h.retClause a := rfl |
| 274 | + |
| 275 | +-- ============================================================ |
| 276 | +-- §14 Subsignature |
| 277 | +-- ============================================================ |
| 278 | + |
| 279 | +def SubSig (sig₁ sig₂ : Sig) : Prop := |
| 280 | + ∀ n, n ∈ sig₁ → n ∈ sig₂ |
| 281 | + |
| 282 | +theorem SubSig.refl : SubSig sig sig := fun _ h => h |
| 283 | + |
| 284 | +theorem SubSig.cons : SubSig sig₁ sig₂ → SubSig sig₁ (n :: sig₂) := |
| 285 | + fun hsub _ hm => List.mem_cons_of_mem n (hsub _ hm) |
| 286 | + |
| 287 | +/-- Lift a computation via a subsignature proof. -/ |
| 288 | +def Free.lift (sub : SubSig sig₁ sig₂) (m : Free sig₁ A) : Free sig₂ A := |
| 289 | + m.weaken sub |
| 290 | + |
| 291 | +theorem Free.lift_pure (sub : SubSig sig₁ sig₂) (a : A) : |
| 292 | + Free.lift sub (Free.pure a : Free sig₁ A) = Free.pure a := rfl |
| 293 | + |
| 294 | +theorem Free.lift_bind (sub : SubSig sig₁ sig₂) (m : Free sig₁ A) (f : A → Free sig₁ B) : |
| 295 | + (m.bind f).lift sub = (m.lift sub).bind (fun a => (f a).lift sub) := by |
| 296 | + exact Free.weaken_bind m f sub |
| 297 | + |
| 298 | +theorem Free.lift_refl (m : Free sig A) : |
| 299 | + m.lift SubSig.refl = m := by |
| 300 | + induction m with |
| 301 | + | pure _ => rfl |
| 302 | + | op n mem p k ih => |
| 303 | + simp [Free.lift, Free.weaken] |
| 304 | + funext r; exact ih r |
| 305 | + |
| 306 | +-- ============================================================ |
| 307 | +-- §15 Map distributes over weaken |
| 308 | +-- ============================================================ |
| 309 | + |
| 310 | +theorem Free.map_weaken (g : A → B) (m : Free sig A) (h : ∀ n, n ∈ sig → n ∈ sig') : |
| 311 | + Free.map g (m.weaken h) = (Free.map g m).weaken h := by |
| 312 | + induction m with |
| 313 | + | pure a => simp [Free.map, Free.bind, Free.weaken] |
| 314 | + | op n mem p k ih => |
| 315 | + simp [Free.map, Free.bind, Free.weaken] |
| 316 | + funext r; exact ih r |
| 317 | + |
| 318 | +-- ============================================================ |
| 319 | +-- §16 handleDeep on pure = retClause (functional form) |
| 320 | +-- ============================================================ |
| 321 | + |
| 322 | +theorem handleDeep_pure_eq_ret (h : DeepHandler opN sig A B) : |
| 323 | + (fun a => handleDeep h (Free.pure a)) = h.retClause := rfl |
| 324 | + |
| 325 | +-- ============================================================ |
| 326 | +-- §17 Identity handler |
| 327 | +-- ============================================================ |
| 328 | + |
| 329 | +/-- An identity deep handler that re-performs the handled operation. -/ |
| 330 | +def idHandler (opN : OpName) (sig : Sig) (A : Type) : |
| 331 | + DeepHandler opN (opN :: sig) A A where |
| 332 | + retClause := Free.pure |
| 333 | + opClause := fun p k => .op opN (.head ..) p k |
| 334 | + |
| 335 | +-- ============================================================ |
| 336 | +-- §18 Handler that discards the operation |
| 337 | +-- ============================================================ |
| 338 | + |
| 339 | +/-- A handler that discards the operation, resuming with a default. -/ |
| 340 | +def discardHandler (opN : OpName) (sig : Sig) (A : Type) (dflt : Nat) : |
| 341 | + DeepHandler opN sig A A where |
| 342 | + retClause := Free.pure |
| 343 | + opClause := fun _ k => k dflt |
| 344 | + |
| 345 | +theorem discardHandler_pure (opN : OpName) (sig : Sig) (a : A) (dflt : Nat) : |
| 346 | + handleDeep (discardHandler opN sig A dflt) (.pure a) = Free.pure a := rfl |
| 347 | + |
| 348 | +-- ============================================================ |
| 349 | +-- §19 Properties of composeRet |
| 350 | +-- ============================================================ |
| 351 | + |
| 352 | +theorem composeRet_apply (f : A → Free sig B) (g : B → Free sig C) (a : A) : |
| 353 | + composeRet f g a = (f a).bind g := rfl |
| 354 | + |
| 355 | +-- ============================================================ |
| 356 | +-- §20 Weakening preserves isPure |
| 357 | +-- ============================================================ |
| 358 | + |
| 359 | +theorem Free.weaken_isPure (a : A) (h : ∀ n, n ∈ sig → n ∈ sig') : |
| 360 | + ((Free.pure a : Free sig A).weaken h).isPure := by |
| 361 | + simp [Free.weaken, Free.isPure] |
| 362 | + |
| 363 | +-- ============================================================ |
| 364 | +-- §21 bind respects weaken |
| 365 | +-- ============================================================ |
| 366 | + |
| 367 | +theorem Free.bind_weaken (m : Free sig A) (f : A → Free sig B) |
| 368 | + (h : ∀ n, n ∈ sig → n ∈ sig') : |
| 369 | + (m.weaken h).bind (fun a => (f a).weaken h) = (m.bind f).weaken h := |
| 370 | + (Free.weaken_bind m f h).symm |
| 371 | + |
| 372 | +-- ============================================================ |
| 373 | +-- §22 Additional Theorems |
| 374 | +-- ============================================================ |
| 375 | + |
| 376 | +/-- Composing map with inject. -/ |
| 377 | +theorem Free.map_inject (g : A → B) (m : Free sig A) : |
| 378 | + Free.map g (m.inject (opN := opN)) = (Free.map g m).inject := by |
| 379 | + exact Free.map_weaken g m _ |
| 380 | + |
| 381 | +/-- Inject is a special case of lift. -/ |
| 382 | +theorem Free.inject_eq_lift (m : Free sig A) : |
| 383 | + m.inject (opN := opN) = m.lift (SubSig.cons SubSig.refl) := by |
| 384 | + induction m with |
| 385 | + | pure _ => rfl |
| 386 | + | op n mem p k ih => exact congrArg (Free.op n _ p) (funext ih) |
| 387 | + |
| 388 | +end AlgebraicEffectsDeep |
0 commit comments