Monad (functional programming)
In functional programming, monads are a way to structure computations as a sequence of steps, where each step not only produces a value but also some extra information about the computation, such as a potential failure, non-determinism, or side effect. More formally, a monad is a type constructor M equipped with two operations, which lifts a value into the monadic context, and which chains monadic computations. In simpler terms, monads can be thought of as interfaces implemented on type constructors, that allow for functions to abstract over various type constructor variants that implement monad.
Both the concept of a monad and the term originally come from category theory, where a monad is defined as an endofunctor with additional structure. Research beginning in the late 1980s and early 1990s established that monads could bring seemingly disparate computer-science problems under a unified, functional model. Category theory also provides a few formal requirements, known as the monad laws, which should be satisfied by any monad and can be used to verify monadic code.
Since monads make semantics explicit for a kind of computation, they can also be used to implement convenient language features. Some languages, such as Haskell, even offer pre-built definitions in their core libraries for the general monad structure and common instances.
Overview
"For a monadm, a value of type m a represents having access to a value of type a within the context of the monad." —C. A. McCannMore exactly, a monad can be used where unrestricted access to a value is inappropriate for reasons specific to the scenario. In the case of the Maybe monad, it is because the value may not exist. In the case of the IO monad, it is because the value may not be known yet, such as when the monad represents user input that will only be provided after a prompt is displayed. In all cases the scenarios in which access makes sense are captured by the bind operation defined for the monad; for the Maybe monad a value is bound only if it exists, and for the IO monad a value is bound only after the previous operations in the sequence have been performed.
A monad can be created by defining a type constructor M and two operations:
-
return :: a -> M a, which receives a value of typeaand wraps it into a monadic value of typeM a, and -
bind :: -> ->, which receives a monadic value of typeM aand a functionfthat accepts values of the base typea. Bind unwrapsM a, appliesfto it, and can process the result offas a monadic valueM b.
Typically, the bind operator
>>= may contain code unique to the monad that performs additional computation steps not available in the function received as a parameter. Between each pair of composed function calls, the bind operator can inject into the monadic value m a some additional information that is not accessible within the function f, and pass it along down the pipeline. It can also exert finer control of the flow of execution, for example by calling the function only under some conditions, or executing the function calls in a particular order.An example: Maybe
One example of a monad is theMaybe type. Undefined null results are one particular pain point that many procedural languages don't provide specific tools for dealing with, requiring use of the null object pattern or checks to test for invalid values at each operation to handle undefined values. This causes bugs and makes it harder to build robust software that gracefully handles errors. The Maybe type forces the programmer to deal with these potentially undefined results by explicitly defining the two states of a result: Just ⌑result⌑, or Nothing. For example, the programmer might be constructing a parser, which is to return an intermediate result, or else signal a condition which the parser has detected, and which the programmer must also handle. With just a little extra functional spice on top, this Maybe type transforms into a fully-featured monad.In most languages, the Maybe monad is also known as an option type, which is just a type that marks whether or not it contains a value. Typically they are expressed as some kind of enumerated type. In the Rust programming language it is called
Option and variants of this type can either be a value of generic type T, or the empty variant: None.// The
enum Option
Option can also be understood as a "wrapping" type, and this is where its connection to monads comes in. In languages with some form of the Maybe type, there are functions that aid in their use such as composing monadic functions with each other and testing if a Maybe contains a value.In the following hard-coded example, a Maybe type is used as a result of functions that may fail, in this case the type returns nothing if there is a divide-by-zero.
// divide -> returns Some
// divide -> returns None
if statements.let m_x = divide; // see divide function above
// The if statement extracts x from m_x if m_x is the Just variant of Maybe
if let Some = m_x else
let result = divide;
match result
fn chainable_division -> Option
chainable_division, Some), Some); // inside chainable_division fails, outside chainable_division returns None
Instead of repeating
Some expressions, we can use something called a bind operator.. This operation takes a monad and a function that returns a monad and runs the function on the inner value of the passed monad, returning the monad from the function.// Rust example using ".map". maybe_x is passed through 2 functions that return Decimal and String respectively.
// As with normal function composition the inputs and outputs of functions feeding into each other should match wrapped types.
let maybe_x: Option
let maybe_result = maybe_x.map.map
halve :: Int -> Maybe Int
halve x
| even x = Just
| odd x = Nothing
-- This code halves x twice. it evaluates to Nothing if x is not a multiple of 4
halve x >>= halve
With
>>= available, chainable_division can be expressed much more succinctly with the help of anonymous functions. Notice in the expression below how the two nested lambdas each operate on the wrapped value in the passed Maybe monad using the bind operator.chainable_division = mx >>=
What has been shown so far is basically a monad, but to be more concise, the following is a strict list of qualities necessary for a monad as defined by the following section.
;Monadic Type
;Unit operation
;Bind operation
These are the 3 things necessary to form a monad. Other monads may embody different logical processes, and some may have additional properties, but all of them will have these three similar components.
Definition
The more common definition for a monad in functional programming, used in the above example, is actually based on a Kleisli triple ⟨T, η, μ⟩ rather than category theory's standard definition. The two constructs turn out to be mathematically equivalent, however, so either definition will yield a valid monad. Given any well-defined basic types and, a monad consists of three parts:- A type constructor that builds up a monadic type
- A type converter, often called unit or return, that embeds an object in the monad:
- A combinator, typically called bind and represented with an infix operator
>>=or a method called flatMap, that unwraps a monadic variable, then inserts it into a monadic function/expression, resulting in a new monadic value:
- is a left-identity for :
- is also a right-identity for :
- is essentially associative:
Usage
The value of the monad pattern goes beyond merely condensing code and providing a link to mathematical reasoning.Whatever language or default programming paradigm a developer uses, following the monad pattern brings many of the benefits of purely functional programming.
By reifying a specific kind of computation, a monad not only encapsulates the tedious details of that computational pattern, but it does so in a declarative way, improving the code's clarity.
As monadic values explicitly represent not only computed values, but computed effects, a monadic expression can be replaced with its value in referentially transparent positions, much like pure expressions can be, allowing for many techniques and optimizations based on rewriting.
Typically, programmers will use to chain monadic functions into a sequence, which has led some to describe monads as "programmable semicolons", a reference to how many imperative languages use semicolons to separate statements.
However, monads do not actually order computations; even in languages that use them as central features, simpler function composition can arrange steps within a program.
A monad's general utility rather lies in simplifying a program's structure and improving separation of concerns through abstraction.
The monad structure can also be seen as a uniquely mathematical and compile time variation on the decorator pattern.
Some monads can pass along extra data that is inaccessible to functions, and some even exert finer control over execution, for example only calling a function under certain conditions.
Because they let application programmers implement domain logic while offloading boilerplate code onto pre-developed modules, monads can even be considered a tool for aspect-oriented programming.
One other noteworthy use for monads is isolating side-effects, like input/output or mutable state, in otherwise purely functional code.
Even purely functional languages can still implement these "impure" computations without monads, via an intricate mix of function composition and continuation-passing style in particular.
With monads though, much of this scaffolding can be abstracted away, essentially by taking each recurring pattern in CPS code and bundling it into a distinct monad.
If a language does not support monads by default, it is still possible to implement the pattern, often without much difficulty.
When translated from category-theory to programming terms, the monad structure is a generic concept and can be defined directly in any language that supports an equivalent feature for bounded polymorphism.
A concept's ability to remain agnostic about operational details while working on underlying types is powerful, but the unique features and stringent behavior of monads set them apart from other concepts.