Free monad
The free monad on a functor is the monad whose values are trees of instructions of shape , with results at the leaves. It supplies the monad operations and satisfies the monad laws, and nothing else, so a program written in it is a description of what to do that can be interpreted afterwards: run for real, run against a test double, printed or analysed. Every way of interpreting the instructions in some monad extends in exactly one way to an interpreter for whole programs[6]. Free monads are a standard way to build embedded languages in Haskell[1], and are closely related to algebraic effects and effect handlers[4].
Definition
Given a functor , the free monad is the recursive type
A value is either finished, with a result, or an instruction of shape whose positions hold the rest of the computation. The monad operations substitute at the leaves:
Bind walks down the tree and grafts onto every leaf; it uses nothing about except . A single instruction is lifted into the monad by putting in its positions, . The monad laws hold because bind is substitution in a tree.
With , a value of the free monad is a result together with a number of steps taken to reach it; with , the one-element type, it is the option monad; with it is the writer monad over lists of .
Programs as data
An instruction set is a functor whose constructors are the operations. An operation that returns an answer holds the rest of the program as a function of the answer; one that returns nothing holds the rest directly. A program built with bind is then a data structure, and running it is a separate function, an interpreter:
A key-value store as a free monad: instructions, a program, and two interpreters.
module type FUNCTOR = sigtype 'a tval map : ('a -> 'b) -> 'a t -> 'b tend(* The free monad on a functor F: a tree of F-shaped nodes with values atthe leaves. *)module Free (F : FUNCTOR) = structtype 'a t = Pure of 'a | Free of 'a t F.tlet return x = Pure xlet rec bind m f = match m with Pure x -> f x | Free fx -> Free (F.map (fun m -> bind m f) fx)let ( let* ) = bindlet lift fx = Free (F.map return fx)end(* The instructions of a key-value store. Each carries the rest of theprogram, as a value or as a function of the instruction's answer. *)module Cmd = structtype 'k t = Get of string * (string option -> 'k) | Put of string * string * 'klet map f = function Get (k, next) -> Get (k, fun v -> f (next v)) | Put (k, v, next) -> Put (k, v, f next)endmodule KV = Free (Cmd)open KVlet get k = lift (Cmd.Get (k, Fun.id))let put k v = lift (Cmd.Put (k, v, ()))(* A program: data only, nothing has run yet. *)let visit user =let* n = get ("visits:" ^ user) inlet n = match n with Some s -> int_of_string s + 1 | None -> 1 inlet* () = put ("visits:" ^ user) (string_of_int n) inlet* () = if n = 1 then put ("first:" ^ user) "yes" else return () inreturn n(* Interpreter 1: run against an in-memory map. *)module SMap = Map.Make (String)let rec run_map store = function| Pure x -> (x, store)| Free (Cmd.Get (k, next)) -> run_map store (next (SMap.find_opt k store))| Free (Cmd.Put (k, v, next)) -> run_map (SMap.add k v store) next(* Interpreter 2: print the commands, answering every get with a fixed value. *)let rec describe answer = function| Pure _ -> []| Free (Cmd.Get (k, next)) -> ("GET " ^ k) :: describe answer (next answer)| Free (Cmd.Put (k, v, next)) -> ("PUT " ^ k ^ " " ^ v) :: describe answer next
Running it.
run_map: result [1; 2; 1]first:ada = yesfirst:bob = yesvisits:ada = 2visits:bob = 1describe (every get answered None):GET visits:adaPUT visits:ada 1PUT first:ada yesdescribe (every get answered Some "41"):GET visits:adaPUT visits:ada 42
The same visit program is run against a map and described as a list of commands. The description shows that the program's behaviour depends on the answers it receives: when the key is missing, it records a first visit. An interpreter could equally send the commands to a database, log them, check them against a permission policy, or replay recorded answers in a test. Swierstra and Altenkirch used this to give a pure semantics to input and output, mutable references and concurrency, so that programs using them can be tested[8].
Universal property
An interpreter for a single instruction is a natural transformation into some monad . The free monad's defining property is that each such extends uniquely to a monad morphism that agrees with it on single instructions[6]:
In Haskell this function is called foldFree. Being a monad morphism, it respects and , so interpreting a program built from two parts is the same as interpreting the parts and combining them in . In categorical terms, is left adjoint to the functor that forgets the monad structure of a monad, and the free monad on exists when is sufficiently well behaved, for example when it is a polynomial functor[6]. The analogy is with the free monoid: lists are sequences of elements with concatenation and nothing else, and a free monad is a sequence of instructions with sequencing and nothing else.
Algebraic effects
Plotkin and Power observed that the monads for many computational effects are free monads on the effect's operations, quotiented by equations between them, such as "writing a location and then reading it returns what was written"[5]. Plotkin and Pretnar's handlers of algebraic effects are the interpreters of such free monads, written as a construct of the language[4], and OCaml 5's effect handlers implement the same idea with continuations captured on the stack instead of a data structure[9].
Performance
Bind traverses the tree to its leaves, so a program built with binds nested to the left, , traverses the growing tree once for every bind and takes time quadratic in its length. It is the same problem as appending to the end of a list repeatedly. The Church-encoded form, also called the codensity transformation, represents a computation as a function of what to do with its result, which reassociates every bind to the right[3]:
Left-nested binds on the free monad and on its Church encoding, counting calls to fmap.
(* Left-nested binds on the free monad are quadratic: each bind walksdown the whole tree built so far. The Church-encoded (codensity) formreassociates them and is linear. *)module Tick = structtype 'k t = Tick of 'klet maps = ref 0let map f (Tick k) = incr maps; Tick (f k)endtype 'a free = Pure of 'a | Free of 'a free Tick.tlet rec bind m f = match m with Pure x -> f x | Free fx -> Free (Tick.map (fun m -> bind m f) fx)let tick = Free (Tick.Tick (Pure ()))(* ((tick >>= fun () -> tick) >>= fun () -> tick) >>= ... n times *)let left_nested n = let rec go m i = if i = 0 then m else go (bind m (fun () -> tick)) (i - 1) in go (Pure ()) nlet rec length = function Pure _ -> 0 | Free (Tick.Tick k) -> 1 + length k(* Church form: a computation is a function of what to do with its result. *)type 'a church = { run : 'r. ('a -> 'r free) -> 'r free }let c_return x = { run = (fun k -> k x) }let c_bind m f = { run = (fun k -> m.run (fun x -> (f x).run k)) }let c_tick = { run = (fun k -> Free (Tick.map k (Tick.Tick ()))) }let c_left_nested n = let rec go m i = if i = 0 then m else go (c_bind m (fun () -> c_tick)) (i - 1) in go (c_return ()) nlet to_free m = m.run (fun x -> Pure x)
Running it.
n free: maps church: maps1000 499500 10002000 1999000 20004000 7998000 40008000 31996000 8000
The plain version makes calls to fmap and the Church-encoded one . The Church form cannot be inspected without being run, so libraries convert between the two. Van der Ploeg and Kiselyov gave a representation that keeps the tree inspectable and makes both building and inspecting efficient, by storing the continuations in a type-aligned sequence[7].
Variants
The freer monad, or operational monad, drops the requirement that the instructions form a functor: an instruction is a GADT constructor whose type index is its answer type, and the continuation is stored separately[2]:
This removes the boilerplate of writing fmap for every instruction set. Swierstra's "data types à la carte" combines instruction sets as sums of functors, so that a program can use operations from several sets and an interpreter can handle one set and pass the rest on[1], which is the basis of extensible-effects libraries[2]. The free applicative, built analogously for applicative functors, gives programs whose sequence of instructions is fixed in advance and can be inspected before any of them runs.
History
Free monads are a construction of category theory, as the monads arising from free algebras[6]. In functional programming they became known as a way of building interpreters in the 2000s: Swierstra and Altenkirch used them to model effects in 2007[8], Swierstra's "data types à la carte" in 2008 showed how to combine them[1], and Voigtländer in the same year showed how to remove the quadratic cost of left-nested binds[3]. Kiselyov and Ishii's freer monads of 2015 simplified the definition[2].
see also
- MonadIn functional programming, a monad is a type constructor m with two operations, return : a -> m a and bind : m a -> (a -> m b) -> m b, satisfying three laws. It lets code with some extra behaviour, such as failure, several results, configuration, state or I/O, be written as a sequence of ordinary steps, with the behaviour defined once in bind. The notion comes from category theory.
- FunctorIn functional programming, a functor is a type constructor f with an operation fmap : (a -> b) -> f a -> f b that applies a function inside the structure without changing its shape, preserving identity and composition. Lists, options, trees and functions out of a fixed type are functors. The notion comes from category theory. In OCaml the word also names parametrised modules such as Map.Make.
- Effect handlerA construct that runs code which may perform an effect, and handles each effect by receiving it together with the continuation from the point where it was performed. OCaml 5 has them, with one-shot continuations: each can be resumed at most once.
- Church encodingChurch encoding represents data in the pure lambda calculus, which has only functions, by functions: a natural number n is the function that applies its argument n times, a boolean is a function choosing between two alternatives, and a pair is a function waiting for a selector. In general a value is represented by its own fold. Arithmetic, logic and data structures all become lambda terms, which is how the lambda calculus was shown to express every computable function; in typed form the encoding needs polymorphism, as in System F.
- GADTGeneralized Algebraic Data Type: an ADT whose constructors may each refine the type parameter of the value they build, instead of every constructor sharing one polymorphic return type. This lets a single well-typed eval return an int for an Add node and a bool for an Eq node, checked at compile time rather than by an unchecked cast.
further reading
- [1]W. Swierstra, “Data types à la carte”, Journal of Functional Programming 18 (2008).
- [2]O. Kiselyov, H. Ishii, “Freer monads, more extensible effects”, Haskell Symposium (2015).
- [3]J. Voigtländer, “Asymptotic improvement of computations over free monads”, Mathematics of Program Construction, LNCS 5133 (2008).
- [4]G. Plotkin, M. Pretnar, “Handlers of algebraic effects”, European Symposium on Programming, LNCS 5502 (2009).
- [5]G. Plotkin, J. Power, “Notions of computation determine monads”, Foundations of Software Science and Computation Structures, LNCS 2303 (2002).
- [6]S. Mac Lane, Categories for the Working Mathematician, ch. IV and VI, Springer (2nd ed., 1998).
- [7]A. van der Ploeg, O. Kiselyov, “Reflection without remorse: revealing a hidden sequence to speed up monadic reflection”, Haskell Symposium (2014).
- [8]W. Swierstra, T. Altenkirch, “Beauty in the beast: a functional semantics for the awkward squad”, Haskell Workshop (2007).
- [9]K. C. Sivaramakrishnan, S. Dolan, L. White, T. Kelly, S. Jaffer, A. Madhavapeddy, “Retrofitting effect handlers onto OCaml”, PLDI (2021).
last updated