wiki

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.

§ 01

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:

§ 02

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 = sig
type t
val empty : t
val ( <+> ) : t -> t -> t
end
module Sum = struct type t = int let empty = 0 let ( <+> ) = ( + ) end
module Product = struct type t = int let empty = 1 let ( <+> ) = ( * ) end
module Max = struct type t = int let empty = min_int let ( <+> ) = max end
module All = struct type t = bool let empty = true let ( <+> ) = ( && ) end
module 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) = struct
type t = A.t * B.t
let 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) = struct
let check gen =
let ok = ref true in
for _ = 1 to 10_000 do
let a = gen () and b = gen () and c = gen () in
let open M in
if (a <+> b) <+> c <> (a <+> (b <+> c)) || empty <+> a <> a || a <+> empty <> a then ok := false
done;
!ok
end

Running it.

sum 31 product 6480 max 9
count 8, max 9, all below 10: true (one pass, one monoid)
concat [(+1); ( * 2); (-3)] applied to 10 = 15
laws: 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 <>.

§ 03

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 be
bracketed 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 in
op (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 499500000
float sum: left fold 14.392726722864989
tree fold 14.392726722865723
(0.1 +. 0.2) +. 0.3 = 0.60000000000000009
0.1 +. (0.2 +. 0.3) = 0.59999999999999998
string 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].

§ 04

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 maps
the 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 monoid
extends 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: true
fold_map into (+): 19
fold_map into (max): 6
fold_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].

§ 05

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.

§ 06

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].

§ 07

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

further reading

  1. [1]N. Jacobson, Basic Algebra I, W. H. Freeman (2nd ed., 1985).
  2. [2]S. Mac Lane, Categories for the Working Mathematician, ch. VII, Springer (2nd ed., 1998).
  3. [3]B. A. Yorgey, “Monoids: theme and variations (functional pearl)”, Haskell Symposium (2012).
  4. [4]R. Hinze, R. Paterson, “Finger trees: a simple general-purpose data structure”, Journal of Functional Programming 16 (2006).
  5. [5]J. Dean, S. Ghemawat, “MapReduce: simplified data processing on large clusters”, OSDI (2004).
  6. [6]G. E. Blelloch, “Prefix sums and their applications”, Technical Report CMU-CS-90-190, Carnegie Mellon University (1990).
  7. [7]D. Goldberg, “What every computer scientist should know about floating-point arithmetic”, ACM Computing Surveys 23 (1991).
  8. [8]G. L. Steele Jr., “Organizing functional code for parallel execution; or, foldl and foldr considered slightly harmful”, invited talk, ICFP (2009).
  9. [9]M.-P. Schützenberger, “On finite monoids having only trivial subgroups”, Information and Control 8 (1965).
  10. [10]N. Bourbaki, Algèbre, ch. I, Hermann (1970).
  11. [11]P. Wadler, “Monads for functional programming”, Advanced Functional Programming, LNCS 925 (1995).

last updated