definition theorem example program
The free monad on an endofunctor is the free monoid in , i.e. the free Monad: the Initial Algebra of the higher-order “list” functor
(coproducts of functors taken pointwise). Its structure map splits into and , which become the two constructors
data FreeMonad f a where
Pure :: a -> FreeMonad f a
Free :: f (FreeMonad f a) -> FreeMonad f aA value is a tree whose nodes are functorfuls of branches and whose leaves hold a’s. It separates what to do from how: a sequence of actions is recorded without committing to an interpreter — like building an AST before compiling, or a list before choosing an algebra.
Sources: DaoFP §14.8 (“Free Monads”: “Category of monads”, “Free monad”, “Free Monad in Haskell”, “Stack calculator example”), Exercises 14.8.1–14.8.3; §12 (Initial Algebra); §15.5 (monad algebras).
- Why not an adjunction? The forgetful (objects: monads; morphisms: natural transformations preserving ) has a left adjoint only for some — “monads tend to blow things up” (size issues) — so free monads are defined as fixed points instead.
- Monad structure:
eta = Pure;mu (Pure fa) = fa; mu (Free ffa) = Free (fmap mu ffa);Pure a >>= k = k a; Free ffa >>= k = Free (fmap (>>= k) ffa).FreeMonadis itself a higher-order functor (hmapalong a natural transformation ). - Interpretation by a monad algebra in the functor category: a carrier endofunctor with , i.e. a pair
type MAlg f g a = (a -> g a, f (g a) -> g a); the catamorphismmcata (l, r) (Pure a) = l a; mcata (l, r) (Free ffa) = r (fmap (mcata (l, r)) ffa). The algebra contains no recursion — recursion is encoded once inmcata; the same program can be run by different algebras (e.g. a pretty printer with carrierConst String, DaoFP Exercise 14.8.3). - Stack calculator EDSL: commands
data StackF k = Push Int k | Top (Int -> k) | Pop k | Add k(the parameterkis the Continuation);liftF fr = Free (fmap Pure fr)and smart constructorspush n = liftF (Push n ()),top = liftF (Top id), …; programs in Do Notation; interpreterrunAlg = (stop, go)with carrier the state functorSt ([Int] -> ([Int], k));run prog = runAction (mcata runAlg prog) []. - Rose trees are
FreeMonad [], non-empty binary treesFreeMonad Bin(DaoFP Exercise 14.8.1, DaoFP Exercise 14.8.2); the List Monad is not free (its join is irreversible).
Docs: plain Julia — Catlab has no dedicated API for this; related: Catlab v0.16 docs · GATlab standard library
# a free monad over a command functor: Pure(a) | Free(f(FreeMonad))
abstract type FreeM end
struct Pure <: FreeM; a; end
struct Free <: FreeM; ffa; end # ffa :: StackF{FreeM}
abstract type StackF{K} end
struct Push{K} <: StackF{K}; n::Int; k::K; end
struct Pop{K} <: StackF{K}; k::K; end
struct Add{K} <: StackF{K}; k::K; end
struct Top{K} <: StackF{K}; ik; end # ik :: Int -> K
fmapS(f, c::Push) = Push{Any}(c.n, f(c.k)); fmapS(f, c::Pop) = Pop{Any}(f(c.k))
fmapS(f, c::Add) = Add{Any}(f(c.k)); fmapS(f, c::Top) = Top{Any}(n -> f(c.ik(n)))
bind(m::Pure, k) = k(m.a)
bind(m::Free, k) = Free(fmapS(x -> bind(x, k), m.ffa))
liftF(c) = Free(fmapS(Pure, c))
push(n) = liftF(Push{Any}(n, nothing)); pop = liftF(Pop{Any}(nothing)); add = liftF(Add{Any}(nothing)); top = liftF(Top{Any}(identity))
calc = bind(push(3), _ -> bind(push(4), _ -> bind(add, _ -> bind(top, x -> bind(pop, _ -> Pure(x))))))
# interpreter = monad algebra with carrier "stack actions" [Int] -> ([Int], k)
run(m::Pure, st) = (st, m.a)
run(m::Free, st) = runc(m.ffa, st)
runc(c::Push, st) = run(c.k, [c.n; st]); runc(c::Pop, st) = run(c.k, st[2:end])
runc(c::Add, st) = run(c.k, [st[1] + st[2]; st[3:end]]); runc(c::Top, st) = run(c.ik(st[1]), st)
run(calc, Int[]) # (Int[], 7)import Mathlib
-- the free monad as an inductive type over a functor-shaped command type
inductive FreeM (f : Type → Type) (α : Type) where
| pure : α → FreeM f α
| free : f (FreeM f α) → FreeM f α
#check @CategoryTheory.Monad -- the category of monads: Monad C with MonadHom
#check @CategoryTheory.MonadHomdata FreeMonad f a where
Pure :: a -> FreeMonad f a
Free :: f (FreeMonad f a) -> FreeMonad f a
instance Functor f => Functor (FreeMonad f) where
fmap g (Pure a) = Pure (g a)
fmap g (Free ffa) = Free (fmap (fmap g) ffa)
instance Functor f => Applicative (FreeMonad f) where
pure = Pure
ff <*> fa = ff >>= \f -> fmap f fa
instance Functor f => Monad (FreeMonad f) where
Pure a >>= k = k a
Free ffa >>= k = Free (fmap (>>= k) ffa)
type MAlg f g a = (a -> g a, f (g a) -> g a)
mcata :: Functor f => MAlg f g a -> FreeMonad f a -> g a
mcata (l, _) (Pure a) = l a
mcata (l, r) (Free ffa) = r (fmap (mcata (l, r)) ffa)
data StackF k = Push Int k | Top (Int -> k) | Pop k | Add k deriving Functor
type FreeStack = FreeMonad StackF
liftF :: Functor f => f r -> FreeMonad f r
liftF fr = Free (fmap Pure fr)
push :: Int -> FreeStack (); push n = liftF (Push n ())
pop :: FreeStack (); pop = liftF (Pop ())
top :: FreeStack Int; top = liftF (Top id)
add :: FreeStack (); add = liftF (Add ())
calc :: FreeStack Int
calc = do { push 3; push 4; add; x <- top; pop; return x }
newtype StackAction k = St ([Int] -> ([Int], k)) deriving Functor
runAction :: StackAction k -> [Int] -> ([Int], k)
runAction (St act) = act
runAlg :: MAlg StackF StackAction a
runAlg = (stop, go) where
stop a = St (\xs -> (xs, a))
go (Pop k) = St (\ns -> runAction k (tail ns))
go (Top ik) = St (\ns -> runAction (ik (head ns)) ns)
go (Push n k) = St (\ns -> runAction k (n : ns))
go (Add k) = St (\ns -> runAction k ((head ns + head (tail ns)) : tail (tail ns)))
run :: FreeMonad StackF k -> ([Int], k)
run prog = runAction (mcata runAlg prog) [] -- run calc == ([], 7)