Monoid
A monoid is a set with an associative binary operation and an identity element for it: the integers under addition with 0, strings under concatenation with the empty string, functions from a set to itself under composition with the identity function. Because the operation is associative, a sequence of elements can be combined with any bracketing, so a computation that combines many values in a monoid can be split into parts and done in parallel, incrementally or in a tree[8]. Lists are the free monoid: any function from elements into a monoid extends in exactly one way to a function from lists that respects the operation. In functional programming, monoids underlie folds, the Foldable abstraction, the writer monad and the annotations of finger trees.
Definition
A monoid is a set with a binary operation and an element such that, for all :
The first law is associativity and the second says that is a two-sided identity. The identity is unique: if and are both identities then . A set with an associative operation but no identity is a semigroup, and a monoid in which every element has an inverse is a group[1]. A monoid is commutative if for all .
Because of associativity, the combination of a finite sequence does not depend on how it is bracketed, and it can be written without brackets as . The identity gives a value to the combination of the empty sequence, so every finite sequence has one:
Examples
Numbers form monoids in several ways: , , and the integers extended with under . Booleans form monoids under conjunction with and disjunction with . Strings and lists form monoids under concatenation, which are not commutative. The functions from a set to itself form a monoid under composition, with the identity function; it is called the endomorphism monoid of . The product of two monoids is a monoid with the operation applied componentwise, and the functions from any set into a monoid form a monoid pointwise.
Monoids as OCaml modules, their product, a generic concat, and a randomized test of the laws.
module type MONOID = sigtype tval empty : tval ( <+> ) : t -> t -> tendmodule Sum = struct type t = int let empty = 0 let ( <+> ) = ( + ) endmodule Product = struct type t = int let empty = 1 let ( <+> ) = ( * ) endmodule Max = struct type t = int let empty = min_int let ( <+> ) = max endmodule All = struct type t = bool let empty = true let ( <+> ) = ( && ) endmodule Str = struct type t = string let empty = "" let ( <+> ) = ( ^ ) end(* Functions from a type to itself, under composition. *)module Endo = struct type t = int -> int let empty = Fun.id let ( <+> ) f g x = f (g x) end(* The product of two monoids is a monoid, componentwise. *)module Pair (A : MONOID) (B : MONOID) = structtype t = A.t * B.tlet empty = (A.empty, B.empty)let ( <+> ) (a, b) (a', b') = (A.( <+> ) a a', B.( <+> ) b b')end(* Combining a whole list needs only the two operations. *)module Concat (M : MONOID) = struct let concat xs = List.fold_left M.( <+> ) M.empty xs end(* The three laws, tested on random elements. *)module Laws (M : MONOID) = structlet check gen =let ok = ref true infor _ = 1 to 10_000 dolet a = gen () and b = gen () and c = gen () inlet open M inif (a <+> b) <+> c <> (a <+> (b <+> c)) || empty <+> a <> a || a <+> empty <> a then ok := falsedone;!okend
Running it.
sum 31 product 6480 max 9count 8, max 9, all below 10: true (one pass, one monoid)concat [(+1); ( * 2); (-3)] applied to 10 = 15laws: Sum true, Max true, Str true
The product monoid computes several results in one traversal: the count, the maximum and a conjunction are combined into a single triple per element. Concatenating a list of functions in the endomorphism monoid composes them, so the rightmost is applied first. OCaml has no type classes, so a monoid is a module and code generic over monoids is a functor; in Haskell it is the Monoid class, with mempty and <>.
Associativity and parallelism
A left fold brackets a sequence as and must run sequentially. For a monoid the balanced bracketing gives the same result, and its two halves are independent, so they can be computed on different processors and combined[8]. With processors, elements are combined in time. Parallel reductions and prefix sums rely on this[6], and so does MapReduce, whose reduce step must be associative for partial results to be combined in any order[5].
A left fold and a balanced tree fold over the same array.
(* Associativity is what allows a fold to be split up: the list can bebracketed any way, so halves can be combined independently. *)let rec fold_tree op empty = function| [||] -> empty| [| x |] -> x| a ->let n = Array.length a / 2 inop (fold_tree op empty (Array.sub a 0 n)) (fold_tree op empty (Array.sub a n (Array.length a - n)))
Running it.
int sum: left fold 499500000 tree fold 499500000float sum: left fold 14.392726722864989tree fold 14.392726722865723(0.1 +. 0.2) +. 0.3 = 0.600000000000000090.1 +. (0.2 +. 0.3) = 0.59999999999999998string concat left and tree agree: true
Integer addition and string concatenation give the same result under either bracketing. Floating-point addition is commutative but not associative, because each operation rounds[7], so a parallel sum of floats can differ in its last digits from a sequential one. Here the tree fold is the more accurate: it adds numbers of similar magnitude, and the left fold adds each small term to an ever larger total.
Incremental computation
Associativity also allows results to be cached and reused. If a sequence is stored in a balanced tree whose nodes hold the combination of their subtrees, changing one element requires recomputing only the combinations on its path to the root, of them, and the combination of any range is available in . Hinze and Paterson's finger trees are parameterized by such a monoid of measures, and the choice of monoid gives sequences, priority queues or interval trees from one data structure[4]. Yorgey surveys how far this goes, including monoids acting on other monoids[3].
Homomorphisms and free monoids
A monoid homomorphism preserves the identity and the operation:
The length of lists is a homomorphism from lists under concatenation to , and the logarithm one from to . A homomorphism can be applied to the parts of a split computation and the results combined, which is what makes the map step of MapReduce compatible with the reduce step.
The lists over a set , written , form the free monoid on : every function into a monoid extends uniquely to a homomorphism , namely
which is the function called foldMap in Haskell. It is "free" because satisfies the monoid laws and nothing more: two lists are equal only if they are equal as sequences.
length as a homomorphism, and fold_map into three different monoids.
(* A monoid homomorphism preserves the unit and the operation. length mapsthe monoid of lists under append to the monoid of integers under +. *)let length_hom xs ys = List.length (xs @ ys) = List.length xs + List.length ys(* Lists are the free monoid: any function from elements into a monoidextends uniquely to a homomorphism from lists, fold_map f. *)let fold_map (empty, op) f xs = List.fold_right (fun x acc -> op (f x) acc) xs empty
Running it.
length (xs @ ys) = length xs + length ys on 1000 random pairs: truefold_map into (+): 19fold_map into (max): 6fold_map into (^): tfmoas
In formal language theory is the free monoid of words over an alphabet, a language is a subset of it, and a finite automaton defines a homomorphism from words into a finite monoid of state transformations. Schützenberger characterized the star-free languages as those whose syntactic monoid has only trivial subgroups[9].
In functional programming
A fold over a collection with a monoid needs no starting value or combining function beyond the monoid's own, which is why Foldable can define sums, counts, searches and conversions to lists from a single foldMap. The writer monad accumulates output in a monoid alongside a computation[11], and the constant functor into a monoid is an applicative functor, which is how a traversal collects values. Monoids that are commutative and idempotent, such as set union and maximum, are the basis of replicated data types that converge regardless of the order in which updates arrive.
Category theory
A monoid is a category with one object: the elements are the arrows, composition is the operation and the identity arrow is . More generally, a monoid object in a monoidal category is an object with arrows and satisfying the associativity and unit laws as commuting diagrams[2]. Ordinary monoids are monoid objects in sets with the cartesian product. A monad is a monoid object in the category of endofunctors, with composition of functors as the tensor, as join and as return[2].
History
Semigroups and monoids were studied in algebra from the early twentieth century as generalizations of groups, and the name monoid was standard by the time Bourbaki used it in the 1970 edition of Algèbre[10]. Free monoids became central to computer science through formal language theory and automata in the 1950s and 1960s[9]. In functional programming, the Monoid class became part of Haskell's standard library, and Steele's 2009 talk made the connection between associativity and parallelism widely known[8].
see also
- SemigroupA semigroup is a set with an associative binary operation, with no requirement of an identity element. Maximum and minimum, union of bounding boxes, concatenation of non-empty lists and "keep the first" are semigroups that are not monoids. Associativity alone allows a non-empty sequence to be combined in any bracketing, and n copies of an element to be combined in O(log n) operations by repeated squaring. Adjoining an identity turns any semigroup into a monoid.
- FoldableFoldable is the abstraction of a container whose elements can be combined in order: a list, a tree, an option. A fold replaces the constructors of the structure with a function and a starting value, and every summary of the elements, their sum, length, maximum, list of elements or whether any satisfies a predicate, is derived from it. Equivalently, a foldable container maps each element into a monoid and combines the results (foldMap). The universal property of fold characterizes exactly which functions are folds.
- 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.
- Finger treeA persistent sequence with amortized constant-time access at both ends, and concatenation and splitting in logarithmic time. The ends are kept in buffers of one to four elements, and the middle is a finger tree of 2-3 nodes, one level deeper at each step down the spine.
- ApplicativeAn applicative functor is a functor with pure : a -> f a and an operation that combines independent computations, <*> : f (a -> b) -> f a -> f b, or equivalently a product f a -> f b -> f (a * b), satisfying four laws. It sits between functors and monads: every monad is applicative, but because later steps cannot depend on earlier results, an applicative computation can collect all errors, run its parts in parallel, or be inspected before it runs.
- RopeA rope is a binary tree whose leaves are strings and whose internal nodes represent the concatenation of their children, with each node caching the length of its left subtree. Concatenation creates one node instead of copying, and indexing, splitting, insertion and deletion in the middle take time logarithmic in the length for a balanced rope. Ropes are immutable and share structure between versions, which makes them suited to text editors with undo and to programs that build long strings piece by piece.
- TraversableTraversable is the abstraction of a container that can be rebuilt while an applicative effect is performed on each element in order: traverse : (a -> f b) -> t a -> f (t b). Validating every element, parsing each string, numbering the nodes of a tree and taking cartesian products are all traversals with different applicative functors. traverse subsumes both map (with the identity functor) and foldMap (with a constant functor into a monoid), and it satisfies laws saying it visits every element exactly once.
further reading
- [1]N. Jacobson, Basic Algebra I, W. H. Freeman (2nd ed., 1985).
- [2]S. Mac Lane, Categories for the Working Mathematician, ch. VII, Springer (2nd ed., 1998).
- [3]B. A. Yorgey, “Monoids: theme and variations (functional pearl)”, Haskell Symposium (2012).
- [4]R. Hinze, R. Paterson, “Finger trees: a simple general-purpose data structure”, Journal of Functional Programming 16 (2006).
- [5]J. Dean, S. Ghemawat, “MapReduce: simplified data processing on large clusters”, OSDI (2004).
- [6]G. E. Blelloch, “Prefix sums and their applications”, Technical Report CMU-CS-90-190, Carnegie Mellon University (1990).
- [7]D. Goldberg, “What every computer scientist should know about floating-point arithmetic”, ACM Computing Surveys 23 (1991).
- [8]G. L. Steele Jr., “Organizing functional code for parallel execution; or, foldl and foldr considered slightly harmful”, invited talk, ICFP (2009).
- [9]M.-P. Schützenberger, “On finite monoids having only trivial subgroups”, Information and Control 8 (1965).
- [10]N. Bourbaki, Algèbre, ch. I, Hermann (1970).
- [11]P. Wadler, “Monads for functional programming”, Advanced Functional Programming, LNCS 925 (1995).
last updated