|
| 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 | +module |
| 8 | + |
| 9 | +public import Cslib.Init |
| 10 | +public import Mathlib.Data.PFunctor.Univariate.Basic |
| 11 | + |
| 12 | +/-! |
| 13 | +# Free Monad of a Polynomial Functor |
| 14 | +
|
| 15 | +We define the free monad on a **polynomial functor** (`PFunctor`), and prove some basic properties. |
| 16 | +
|
| 17 | +The free monad `PFunctor.FreeM P` extends the W-type construction with an extra `pure` |
| 18 | +constructor, yielding a monad that is free over the polynomial functor `P`. |
| 19 | +
|
| 20 | +## Comparison with `Cslib.FreeM` |
| 21 | +
|
| 22 | +`Cslib.FreeM F` (in `Cslib/Foundations/Control/Monad/Free.lean`) builds a free monad over an |
| 23 | +arbitrary type constructor `F : Type u → Type v`, which need not be functorial. |
| 24 | +Its `liftBind` constructor abstracts over the intermediate type `ι`: |
| 25 | +``` |
| 26 | +| liftBind {ι : Type u} (op : F ι) (cont : ι → FreeM F α) : FreeM F α |
| 27 | +``` |
| 28 | +
|
| 29 | +`PFunctor.FreeM P` instead takes a polynomial functor `P : PFunctor`, where the shapes |
| 30 | +`P.A` and positions `P.B a` are given explicitly. |
| 31 | +Its `liftBind` constructor uses the shape and continuation directly: |
| 32 | +``` |
| 33 | +| liftBind (a : P.A) (cont : P.B a → P.FreeM α) : P.FreeM α |
| 34 | +``` |
| 35 | +
|
| 36 | +When the effect signature is naturally polynomial (a fixed set of operations, each with a |
| 37 | +known return type), `PFunctor.FreeM` avoids the universe bump that the abstract `ι` in |
| 38 | +`Cslib.FreeM` introduces. |
| 39 | +Concretely, `PFunctor.FreeM P` is a genuine endofunctor on a single universe: for a ground |
| 40 | +`P`, `P.FreeM α : Type` whenever `α : Type`, whereas `Cslib.FreeM F α : Type 1` for |
| 41 | +`F : Type → Type`, since `liftBind` stores the intermediate type `ι : Type`. |
| 42 | +
|
| 43 | +This matters when a program must itself be a first-class value of the same kind, i.e. for |
| 44 | +higher-order effects whose operations consume or return computations of the same monad |
| 45 | +(schedulers, exception handlers, staged interpreters, higher-order oracles). |
| 46 | +Such an effect's response type can be another `P.FreeM` computation, staying in one universe: |
| 47 | +``` |
| 48 | +def coin : PFunctor.{0,0} := ⟨Bool, fun b => if b then Bool else Nat⟩ |
| 49 | +-- a `coin`-program is itself `Type 0`, so it can be another effect's response type: |
| 50 | +def scheduler : PFunctor.{0,0} := ⟨Unit, fun _ => coin.FreeM Bool⟩ -- `Type 0` |
| 51 | +``` |
| 52 | +With the abstract `ι`, the analogous program lives in `Type 1`, so an effect `Type → Type` |
| 53 | +cannot return it; bumping the effect to `Type 1 → Type 1` pushes its programs to `Type 2`, |
| 54 | +and so on without bound. |
| 55 | +
|
| 56 | +This construction is ported from the [VCV-io](https://github.com/dtumad/VCV-io) library. |
| 57 | +
|
| 58 | +## Main Definitions |
| 59 | +
|
| 60 | +- `PFunctor.FreeM`: The free monad on a polynomial functor. |
| 61 | +- `PFunctor.FreeM.lift`: Lift a shape of the base polynomial functor into the free monad. |
| 62 | +- `PFunctor.FreeM.liftObj`: Lift an object of the base polynomial functor into the free monad. |
| 63 | +- `PFunctor.FreeM.liftM`: Interpret `FreeM P` into any other monad. |
| 64 | +-/ |
| 65 | + |
| 66 | +@[expose] public section |
| 67 | + |
| 68 | +universe u v uA uB |
| 69 | + |
| 70 | +namespace PFunctor |
| 71 | + |
| 72 | +-- Disable generation of unneeded lemmas which the simpNF linter would complain about. |
| 73 | +set_option genInjectivity false in |
| 74 | +set_option genSizeOfSpec false in |
| 75 | +/-- The free monad on a polynomial functor. |
| 76 | +This extends `WType` with an extra `pure` constructor. -/ |
| 77 | +inductive FreeM (P : PFunctor.{uA, uB}) : Type v → Type (max uA uB v) |
| 78 | + /-- A leaf node wrapping a pure value. -/ |
| 79 | + | protected pure {α} (a : α) : P.FreeM α |
| 80 | + /-- Invoke the operation `a : P.A` with continuation `cont : P.B a → P.FreeM α`. -/ |
| 81 | + | liftBind {α} (a : P.A) (cont : P.B a → P.FreeM α) : P.FreeM α |
| 82 | +deriving Inhabited |
| 83 | + |
| 84 | +namespace FreeM |
| 85 | + |
| 86 | +variable {P : PFunctor.{uA, uB}} {α β γ : Type*} |
| 87 | + |
| 88 | +instance : Pure (P.FreeM) where pure := .pure |
| 89 | + |
| 90 | +@[simp] |
| 91 | +theorem pure_eq_pure : (FreeM.pure : α → P.FreeM α) = pure := rfl |
| 92 | + |
| 93 | +/-- Lift a shape of the base polynomial functor into the free monad. -/ |
| 94 | +def lift (a : P.A) : P.FreeM (P.B a) := FreeM.liftBind a pure |
| 95 | + |
| 96 | +@[simp] lemma lift_ne_pure (a : P.A) (y : P.B a) : |
| 97 | + (lift a : P.FreeM (P.B a)) ≠ pure y := by simp [lift] |
| 98 | + |
| 99 | +@[simp] lemma pure_ne_lift (a : P.A) (y : P.B a) : |
| 100 | + pure y ≠ (lift a : P.FreeM (P.B a)) := by simp [lift] |
| 101 | + |
| 102 | +/-- Bind operation for the `FreeM` monad. |
| 103 | +
|
| 104 | +The builtin `>>=` notation should be preferred when `α` and `β` are in the same universe. -/ |
| 105 | +protected def bind : P.FreeM α → (α → P.FreeM β) → P.FreeM β |
| 106 | + | FreeM.pure a, f => f a |
| 107 | + | FreeM.liftBind a cont, f => FreeM.liftBind a (fun u ↦ FreeM.bind (cont u) f) |
| 108 | + |
| 109 | +instance : Bind (P.FreeM) where bind := .bind |
| 110 | + |
| 111 | +/-- Note that this lemma does not always apply, as it is universe-constrained by `Bind.bind`. -/ |
| 112 | +@[simp] |
| 113 | +theorem bind_eq_bind {α β : Type v} : |
| 114 | + (FreeM.bind : P.FreeM α → _ → P.FreeM β) = Bind.bind := rfl |
| 115 | + |
| 116 | +/-- Map a function over a `FreeM` computation. |
| 117 | +
|
| 118 | +The builtin `<$>` notation should be preferred when `α` and `β` are in the same universe. -/ |
| 119 | +def map (f : α → β) : P.FreeM α → P.FreeM β |
| 120 | + | .pure a => .pure (f a) |
| 121 | + | .liftBind a cont => .liftBind a fun u => FreeM.map f (cont u) |
| 122 | + |
| 123 | +instance : Functor (P.FreeM) where |
| 124 | + map := .map |
| 125 | + |
| 126 | +/-- Note that this lemma does not always apply, as it is universe-constrained by `Functor.map`. -/ |
| 127 | +@[simp] |
| 128 | +theorem map_eq_map {α β : Type v} : |
| 129 | + FreeM.map (P := P) (α := α) (β := β) = Functor.map := rfl |
| 130 | + |
| 131 | +@[simp] |
| 132 | +lemma liftBind_eq (a : P.A) (cont : P.B a → P.FreeM α) : |
| 133 | + FreeM.liftBind a cont = (FreeM.lift a).bind cont := rfl |
| 134 | + |
| 135 | +/-- Lift an object of the base polynomial functor into the free monad. |
| 136 | +
|
| 137 | +This lifts the shape `x.1` with `lift` and relabels the responses with `x.2`. We use the |
| 138 | +universe-polymorphic `FreeM.map` rather than `<$>`, since the response type `P.B x.1` and the |
| 139 | +target `α` need not lie in the same universe. -/ |
| 140 | +abbrev liftObj (x : P.Obj α) : P.FreeM α := (lift x.1).map x.2 |
| 141 | + |
| 142 | +instance : MonadLift P (P.FreeM) where |
| 143 | + monadLift x := FreeM.liftObj x |
| 144 | + |
| 145 | +@[simp] lemma liftObj_ne_pure (x : P.Obj α) (y : α) : |
| 146 | + (liftObj x : P.FreeM α) ≠ pure y := by simp [liftObj, lift, map, -liftBind_eq] |
| 147 | + |
| 148 | +@[simp] lemma pure_ne_liftObj (x : P.Obj α) (y : α) : |
| 149 | + pure y ≠ (liftObj x : P.FreeM α) := by simp [liftObj, lift, map, -liftBind_eq] |
| 150 | + |
| 151 | +lemma monadLift_eq_liftObj (x : P.Obj α) : (x : P.FreeM α) = FreeM.liftObj x := rfl |
| 152 | + |
| 153 | +set_option linter.unusedVariables false in |
| 154 | +/-- An override for the default induction principle that is in simp-normal form. |
| 155 | +
|
| 156 | +Note that when `α` and `P.B a` are in the same universe, this simplifies slightly further. -/ |
| 157 | +@[induction_eliminator] |
| 158 | +protected theorem induction {motive : P.FreeM α → Prop} |
| 159 | + (pure : ∀ a, motive (pure a)) |
| 160 | + (lift_bind : ∀ (a : P.A) (cont : P.B a → P.FreeM α) (ih : ∀ i, motive (cont i)), |
| 161 | + motive ((FreeM.lift a).bind cont)) : ∀ x, motive x |
| 162 | + | .pure a => pure a |
| 163 | + | liftBind a cont => lift_bind a cont fun u => FreeM.induction pure lift_bind (cont u) |
| 164 | + |
| 165 | +protected theorem bind_assoc (x : P.FreeM α) (f : α → P.FreeM β) (g : β → P.FreeM γ) : |
| 166 | + (x.bind f).bind g = x.bind (fun a => (f a).bind g) := by |
| 167 | + induction x with |
| 168 | + | pure a => rfl |
| 169 | + | lift_bind a cont ih => simp [← liftBind_eq, FreeM.bind, ih] at * |
| 170 | + |
| 171 | +/-- `.pure a` followed by `bind` collapses immediately. -/ |
| 172 | +@[simp] |
| 173 | +lemma pure_bind (a : α) (f : α → P.FreeM β) : |
| 174 | + (pure a : P.FreeM α).bind f = f a := rfl |
| 175 | + |
| 176 | +@[simp] |
| 177 | +lemma bind_pure : ∀ x : P.FreeM α, x.bind pure = x |
| 178 | + | .pure a => rfl |
| 179 | + | .liftBind a cont => by |
| 180 | + simp only [FreeM.bind]; congr 1; funext u; exact bind_pure (cont u) |
| 181 | + |
| 182 | +@[simp] |
| 183 | +lemma bind_pure_comp (f : α → β) : ∀ x : P.FreeM α, x.bind (pure ∘ f) = map f x |
| 184 | + | .pure a => rfl |
| 185 | + | .liftBind a cont => by simp only [FreeM.bind, map, bind_pure_comp] |
| 186 | + |
| 187 | +@[simp] |
| 188 | +lemma liftBind_bind (a : P.A) (cont : P.B a → P.FreeM β) (f : β → P.FreeM γ) : |
| 189 | + ((FreeM.lift a).bind cont).bind f = (FreeM.lift a).bind (fun u ↦ (cont u).bind f) := by |
| 190 | + simp only [lift] |
| 191 | + exact FreeM.bind_assoc (FreeM.liftBind a pure) cont f |
| 192 | + |
| 193 | +@[simp] |
| 194 | +lemma liftObj_bind (x : P.Obj α) (f : α → P.FreeM β) : |
| 195 | + (FreeM.liftObj x).bind f = FreeM.liftBind x.1 (fun a ↦ f (x.2 a)) := rfl |
| 196 | + |
| 197 | +@[simp] lemma bind_eq_pure_iff (x : P.FreeM α) (f : α → P.FreeM β) (b : β) : |
| 198 | + x.bind f = pure b ↔ ∃ a, x = pure a ∧ f a = pure b := by |
| 199 | + cases x with |
| 200 | + | pure a => exact ⟨fun h => ⟨a, rfl, h⟩, fun ⟨_, h, hf⟩ => by cases h; exact hf⟩ |
| 201 | + | liftBind a cont => |
| 202 | + constructor |
| 203 | + · intro h |
| 204 | + cases h |
| 205 | + · rintro ⟨_, h, _⟩ |
| 206 | + cases h |
| 207 | + |
| 208 | +@[simp] lemma pure_eq_bind_iff (x : P.FreeM α) (f : α → P.FreeM β) (b : β) : |
| 209 | + pure b = x.bind f ↔ ∃ a, x = pure a ∧ pure b = f a := by |
| 210 | + cases x with |
| 211 | + | pure a => exact ⟨fun h => ⟨a, rfl, h⟩, fun ⟨_, h, hf⟩ => by cases h; exact hf⟩ |
| 212 | + | liftBind a cont => |
| 213 | + constructor |
| 214 | + · intro h |
| 215 | + cases h |
| 216 | + · rintro ⟨_, h, _⟩ |
| 217 | + cases h |
| 218 | + |
| 219 | +instance : Monad (P.FreeM) where |
| 220 | + |
| 221 | +@[simp] |
| 222 | +theorem id_map : ∀ x : P.FreeM α, map id x = x |
| 223 | + | .pure a => rfl |
| 224 | + | .liftBind a cont => by |
| 225 | + simp only [map] |
| 226 | + congr 1 |
| 227 | + funext u |
| 228 | + exact id_map (cont u) |
| 229 | + |
| 230 | +theorem comp_map (h : β → γ) (g : α → β) : |
| 231 | + ∀ x : P.FreeM α, map (h ∘ g) x = map h (map g x) |
| 232 | + | .pure a => rfl |
| 233 | + | .liftBind a cont => by |
| 234 | + simp only [map] |
| 235 | + congr 1 |
| 236 | + funext u |
| 237 | + exact comp_map h g (cont u) |
| 238 | + |
| 239 | +instance : LawfulMonad (P.FreeM) := LawfulMonad.mk' |
| 240 | + (bind_pure_comp := bind_pure_comp) |
| 241 | + (id_map := id_map) |
| 242 | + (pure_bind := pure_bind) |
| 243 | + (bind_assoc := FreeM.bind_assoc) |
| 244 | + |
| 245 | +@[simp] |
| 246 | +lemma pure_inj (a b : α) : (pure a : P.FreeM α) = pure b ↔ a = b := by |
| 247 | + constructor |
| 248 | + · intro h |
| 249 | + cases h |
| 250 | + rfl |
| 251 | + · rintro rfl; rfl |
| 252 | + |
| 253 | +lemma liftBind_inj (a a' : P.A) |
| 254 | + (cont : P.B a → P.FreeM α) (cont' : P.B a' → P.FreeM α) : |
| 255 | + FreeM.liftBind a cont = FreeM.liftBind a' cont' ↔ ∃ h : a = a', h ▸ cont = cont' := by |
| 256 | + constructor |
| 257 | + · intro h |
| 258 | + cases h |
| 259 | + exact ⟨rfl, rfl⟩ |
| 260 | + · rintro ⟨rfl, rfl⟩ |
| 261 | + rfl |
| 262 | + |
| 263 | +section liftM |
| 264 | + |
| 265 | +variable {m : Type uB → Type v} {α : Type uB} |
| 266 | + |
| 267 | +/-- Interpret a `FreeM P` computation into any monad `m` by providing an interpretation |
| 268 | +`interp : (a : P.A) → m (P.B a)` for each operation. -/ |
| 269 | +protected def liftM [Pure m] [Bind m] (interp : (a : P.A) → m (P.B a)) : P.FreeM α → m α |
| 270 | + | .pure a => pure a |
| 271 | + | .liftBind a cont => interp a >>= fun u ↦ (cont u).liftM interp |
| 272 | + |
| 273 | +variable [Monad m] (interp : (a : P.A) → m (P.B a)) |
| 274 | + |
| 275 | +@[simp] |
| 276 | +lemma liftM_pure (a : α) : (Pure.pure a : P.FreeM α).liftM interp = Pure.pure a := rfl |
| 277 | + |
| 278 | +@[simp] |
| 279 | +lemma liftM_lift_bind (a : P.A) (cont : P.B a → P.FreeM α) : |
| 280 | + FreeM.liftM interp (FreeM.lift a >>= cont) = |
| 281 | + (do let u ← interp a; (cont u).liftM interp) := by |
| 282 | + dsimp only [FreeM.liftM, FreeM.bind, FreeM.lift] |
| 283 | + rfl |
| 284 | + |
| 285 | +/-- |
| 286 | +A predicate stating that `eval : P.FreeM α → m α` is an interpreter for the polynomial |
| 287 | +effect handler `handler : (a : P.A) → m (P.B a)`. |
| 288 | +
|
| 289 | +This means that `eval` is a monad morphism from the free monad `P.FreeM` to the |
| 290 | +monad `m`, and that it extends the interpretation of individual operations given by |
| 291 | +`handler`. |
| 292 | +-/ |
| 293 | +structure Interprets (handler : (a : P.A) → m (P.B a)) (eval : P.FreeM α → m α) : Prop where |
| 294 | + apply_pure (a : α) : eval (.pure a) = pure a |
| 295 | + apply_lift_bind (a : P.A) (cont : P.B a → P.FreeM α) : |
| 296 | + eval ((FreeM.lift a).bind cont) = handler a >>= fun x => eval (cont x) |
| 297 | + |
| 298 | +theorem Interprets.eq {handler : (a : P.A) → m (P.B a)} {eval : P.FreeM α → m α} |
| 299 | + (h : Interprets handler eval) : |
| 300 | + eval = (·.liftM handler) := by |
| 301 | + ext x |
| 302 | + induction x with |
| 303 | + | pure a => exact h.apply_pure a |
| 304 | + | lift_bind a cont ih => |
| 305 | + rw [h.apply_lift_bind] |
| 306 | + conv_rhs => simp only [bind_eq_bind, liftM_lift_bind] |
| 307 | + simp only [ih] |
| 308 | + |
| 309 | +theorem Interprets.liftM (handler : (a : P.A) → m (P.B a)) : |
| 310 | + Interprets handler (·.liftM handler : P.FreeM α → _) where |
| 311 | + apply_pure _ := rfl |
| 312 | + apply_lift_bind _ _ := rfl |
| 313 | + |
| 314 | +/-- |
| 315 | +The universal property of the free monad `P.FreeM`. |
| 316 | +
|
| 317 | +That is, `liftM handler` is the unique interpreter that extends the effect handler `handler` to |
| 318 | +interpret `P.FreeM` computations in a monad `m`. |
| 319 | +-/ |
| 320 | +theorem Interprets.iff (handler : (a : P.A) → m (P.B a)) (eval : P.FreeM α → m α) : |
| 321 | + Interprets handler eval ↔ eval = (·.liftM handler) := |
| 322 | + ⟨(·.eq), fun h => h ▸ Interprets.liftM _⟩ |
| 323 | + |
| 324 | +variable [LawfulMonad m] |
| 325 | + |
| 326 | +@[simp] |
| 327 | +lemma liftM_bind {α β : Type uB} (x : P.FreeM α) (f : α → P.FreeM β) : |
| 328 | + (x >>= f).liftM interp = (do let u ← x.liftM interp; (f u).liftM interp) := by |
| 329 | + induction x with |
| 330 | + | pure _ => simp only [liftM_pure, LawfulMonad.pure_bind] |
| 331 | + | lift_bind a cont h => |
| 332 | + simp_rw [bind_eq_bind] |
| 333 | + rw [LawfulMonad.bind_assoc, liftM_lift_bind] |
| 334 | + simp_rw [liftM_lift_bind, LawfulMonad.bind_assoc] |
| 335 | + congr 1 |
| 336 | + funext u |
| 337 | + exact h u |
| 338 | + |
| 339 | +@[simp] |
| 340 | +lemma liftM_map {α β : Type uB} (f : α → β) (x : P.FreeM α) : |
| 341 | + (f <$> x).liftM interp = f <$> x.liftM interp := by |
| 342 | + simp_rw [← LawfulMonad.bind_pure_comp, liftM_bind, liftM_pure] |
| 343 | + |
| 344 | +@[simp] |
| 345 | +lemma liftM_seq {α β : Type uB} |
| 346 | + (interp : (a : P.A) → m (P.B a)) (x : P.FreeM (α → β)) (y : P.FreeM α) : |
| 347 | + (x <*> y).liftM interp = x.liftM interp <*> y.liftM interp := by |
| 348 | + simp [seq_eq_bind_map] |
| 349 | + |
| 350 | +@[simp] |
| 351 | +lemma liftM_seqLeft {α β : Type uB} |
| 352 | + (interp : (a : P.A) → m (P.B a)) (x : P.FreeM α) (y : P.FreeM β) : |
| 353 | + (x <* y).liftM interp = x.liftM interp <* y.liftM interp := by |
| 354 | + simp [seqLeft_eq_bind] |
| 355 | + |
| 356 | +@[simp] |
| 357 | +lemma liftM_seqRight {α β : Type uB} |
| 358 | + (interp : (a : P.A) → m (P.B a)) (x : P.FreeM α) (y : P.FreeM β) : |
| 359 | + (x *> y).liftM interp = x.liftM interp *> y.liftM interp := by |
| 360 | + simp [seqRight_eq_bind] |
| 361 | + |
| 362 | +@[simp] |
| 363 | +lemma liftM_lift (interp : (a : P.A) → m (P.B a)) (a : P.A) : |
| 364 | + (FreeM.lift a).liftM interp = interp a := by |
| 365 | + simpa [bind_pure] using |
| 366 | + (liftM_lift_bind (interp := interp) (a := a) (cont := pure)) |
| 367 | + |
| 368 | +@[simp] |
| 369 | +lemma liftM_liftObj (interp : (a : P.A) → m (P.B a)) (x : P.Obj α) : |
| 370 | + (FreeM.liftObj x).liftM interp = x.2 <$> interp x.1 := by |
| 371 | + simp [liftObj] |
| 372 | + |
| 373 | +end liftM |
| 374 | + |
| 375 | +end FreeM |
| 376 | + |
| 377 | +end PFunctor |
0 commit comments