Free Monad of a Polynomial Functor #
We define the free monad on a polynomial functor (PFunctor), and prove some basic properties.
The free monad PFunctor.FreeM P extends the W-type construction with an extra pure
constructor, yielding a monad that is free over the polynomial functor P.
Comparison with Cslib.FreeM #
Cslib.FreeM F (in Cslib/Foundations/Control/Monad/Free.lean) builds a free monad over an
arbitrary type constructor F : Type u → Type v, which need not be functorial.
Its liftBind constructor abstracts over the intermediate type ι:
| liftBind {ι : Type u} (op : F ι) (cont : ι → FreeM F α) : FreeM F α
PFunctor.FreeM P instead takes a polynomial functor P : PFunctor, where the shapes
P.A and positions P.B a are given explicitly.
Its liftBind constructor uses the shape and continuation directly:
| liftBind (a : P.A) (cont : P.B a → P.FreeM α) : P.FreeM α
When the effect signature is naturally polynomial (a fixed set of operations, each with a
known return type), PFunctor.FreeM avoids the universe bump that the abstract ι in
Cslib.FreeM introduces.
Concretely, PFunctor.FreeM P is a genuine endofunctor on a single universe: for a ground
P, P.FreeM α : Type whenever α : Type, whereas Cslib.FreeM F α : Type 1 for
F : Type → Type, since liftBind stores the intermediate type ι : Type.
This matters when a program must itself be a first-class value of the same kind, i.e. for
higher-order effects whose operations consume or return computations of the same monad
(schedulers, exception handlers, staged interpreters, higher-order oracles).
Such an effect's response type can be another P.FreeM computation, staying in one universe:
def coin : PFunctor.{0,0} := ⟨Bool, fun b => if b then Bool else Nat⟩
-- a `coin`-program is itself `Type 0`, so it can be another effect's response type:
def scheduler : PFunctor.{0,0} := ⟨Unit, fun _ => coin.FreeM Bool⟩ -- `Type 0`
With the abstract ι, the analogous program lives in Type 1, so an effect Type → Type
cannot return it; bumping the effect to Type 1 → Type 1 pushes its programs to Type 2,
and so on without bound.
This construction is ported from the VCV-io library.
Main Definitions #
PFunctor.FreeM: The free monad on a polynomial functor.PFunctor.FreeM.lift: Lift a shape of the base polynomial functor into the free monad.PFunctor.FreeM.liftObj: Lift an object of the base polynomial functor into the free monad.PFunctor.FreeM.liftM: InterpretFreeM Pinto any other monad.
The free monad on a polynomial functor.
This extends WType with an extra pure constructor.
- pure
{P : PFunctor.{uA, uB}}
{α : Type v}
(a : α)
: P.FreeM α
A leaf node wrapping a pure value.
- liftBind
{P : PFunctor.{uA, uB}}
{α : Type v}
(a : P.A)
(cont : P.B a → P.FreeM α)
: P.FreeM α
Invoke the operation
a : P.Awith continuationcont : P.B a → P.FreeM α.
Instances For
Equations
Equations
- PFunctor.FreeM.instPure = { pure := fun {α : Type ?u.1} => PFunctor.FreeM.pure }
Lift a shape of the base polynomial functor into the free monad.
Equations
Instances For
Bind operation for the FreeM monad.
The builtin >>= notation should be preferred when α and β are in the same universe.
Equations
- (PFunctor.FreeM.pure a).bind x✝ = x✝ a
- (PFunctor.FreeM.liftBind a cont).bind x✝ = PFunctor.FreeM.liftBind a fun (u : P.B a) => (cont u).bind x✝
Instances For
Equations
- PFunctor.FreeM.instBind = { bind := fun {α β : Type ?u.1} => PFunctor.FreeM.bind }
Note that this lemma does not always apply, as it is universe-constrained by Bind.bind.
Map a function over a FreeM computation.
The builtin <$> notation should be preferred when α and β are in the same universe.
Equations
- PFunctor.FreeM.map f (PFunctor.FreeM.pure a) = PFunctor.FreeM.pure (f a)
- PFunctor.FreeM.map f (PFunctor.FreeM.liftBind a cont) = PFunctor.FreeM.liftBind a fun (u : P.B a) => PFunctor.FreeM.map f (cont u)
Instances For
Equations
- PFunctor.FreeM.instFunctor = { map := fun {α β : Type ?u.1} => PFunctor.FreeM.map }
Note that this lemma does not always apply, as it is universe-constrained by Functor.map.
Lift an object of the base polynomial functor into the free monad.
This lifts the shape x.1 with lift and relabels the responses with x.2. We use the
universe-polymorphic FreeM.map rather than <$>, since the response type P.B x.1 and the
target α need not lie in the same universe.
Equations
Instances For
Equations
- PFunctor.FreeM.instMonadLiftObj = { monadLift := fun {α : Type ?u.1} (x : ↑P α) => PFunctor.FreeM.liftObj x }
An override for the default induction principle that is in simp-normal form.
Note that when α and P.B a are in the same universe, this simplifies slightly further.
Equations
- One or more equations did not get rendered due to their size.
Interpret a FreeM P computation into any monad m by providing an interpretation
interp : (a : P.A) → m (P.B a) for each operation.
Equations
- PFunctor.FreeM.liftM interp (PFunctor.FreeM.pure a) = pure a
- PFunctor.FreeM.liftM interp (PFunctor.FreeM.liftBind a cont) = do let u ← interp a PFunctor.FreeM.liftM interp (cont u)
Instances For
A predicate stating that eval : P.FreeM α → m α is an interpreter for the polynomial
effect handler handler : (a : P.A) → m (P.B a).
This means that eval is a monad morphism from the free monad P.FreeM to the
monad m, and that it extends the interpretation of individual operations given by
handler.
Instances For
The universal property of the free monad P.FreeM.
That is, liftM handler is the unique interpreter that extends the effect handler handler to
interpret P.FreeM computations in a monad m.