Overview

10 Monads

This chapter explains how broadly applicable abstractions let you write uniform solutions to diverse problems in C++. It reframes functors in the functional sense (not callable objects) as wrappers that support a transform/map operation obeying identity and composition, allowing ordinary functions to be lifted over values inside containers like ranges, vectors, and optionals. While transform works well for wrapping once, it breaks down when functions themselves return wrapped results, creating unwieldy nesting (wrapper-of-wrapper) that hinders composition.

Monads solve this by extending functors with a way to remove one layer of wrapping while composing computations that return wrapped values. Equivalently, they provide a constructor plus bind (mbind) that combines transform with flattening and satisfies familiar monad laws. The chapter develops this through concrete cases: collections/ranges where mbind acts like flatMap (enabling expressive filtering and range/monad comprehensions); failure handling with optional (short-circuit on absence) and expected (propagate rich errors); adapters that bridge exceptions into monadic flows; and a Writer-like with_log type that threads and accumulates logs alongside results without side effects.

The same pattern extends to asynchrony: treating futures as monads lets you transform results without blocking and chain asynchronous steps without creating futures-of-futures; continuations (then) make mbind straightforward even when standard futures otherwise require blocking get. Finally, the chapter composes monadic functions themselves (Kleisli composition) so larger behaviors emerge from small, reusable pieces. The unifying takeaway is that monads offer a clean, compositional way to thread context—collections, absence, errors, state, and concurrency—through C++ programs while keeping core computations simple and reusable.

A functor is a class template over a type T. You can lift any function operating on T to work on instances of that class template.
c10_01.png
The main rule the transform function needs to obey is that composing two transforms—one that lifts function f and another that lifts function g—needs to have the same effect as having a single transformation with the composition of f and g.
c10_02.png
An optional is a wrapper type that can contain a single value or can contain nothing. If you transform an optional that contains a value, you get an optional containing the transformed value. If the optional didn’t contain anything, you get an empty optional as the result.
c10_03.png
You can apply a chain of lifted functions to an optional value. In the end, you get an optional object containing the result of all transformations composed.
c10_04.png
If you try to compose multiple functions that take a value and return an instance of a functor, you’ll start getting nested functors. In this case, you get an optional of an optional of some type, which is mostly useless. What’s more, in order to chain two transformations, you’d need to lift the second one twice.
c10_05.png
Transforming a functor with a function that doesn’t return a value, but rather a new instance of that functor, results in nested functor types. You can create a function that will remove nesting.
c10_06.png
You can use optional values to denote errors in computations. With join, you can easily chain multiple transformations that can fail.
c10_07.png
Transforming a vector gives you a new vector with the same number of elements. For each element in the original collection, you get an element in the result.
c10_08.png
The mbind function for vectors applies the transformation function to each element in the original vector. The result of each transformation is a vector of elements. All elements from those vectors are collected into a single resulting vector. Unlike transform, mbind allows you to have not only a single resulting element per input element, but as many as you want.
c10_09.png
You can easily express filtering with mbind by passing it a transformation function that returns an empty collection if the original element doesn’t satisfy the filtering predicate, or a collection containing one element otherwise.
c10_10.png
To perform error handling with optional values instead of exceptions, you can chain functions that return optional values with mbind. The chain will be broken as soon as any of the transformations fails and returns an empty optional. If all the transformations succeed, you’ll get an optional containing the resulting value.
c10_11.png
When using expected for error handling, you can chain multiple transformations that can fail, just as with optional values. As soon as you encounter an error, the transformations will stop, and you’ll get that error in the resulting expected object. If there is no error, you’ll get the transformed value.
c10_12.png
Unlike the previous monads, such as optional and expected, where the result depended only on the last transformation, with_log goes a bit further. You’re collecting the log from all the transformations performed.
c10_13.png
When you call a function from the main program, the program is blocked until the function finishes. If the function is slow, the program can be blocked for a long time, and it could use that time to perform other tasks.
c10_14.png
Instead of waiting for the slow function to finish, it’s better to have that function return a handler you can use to access the value after it’s calculated.
c10_15.png
Monadic binding allows you to chain multiple asynchronous operations. The result is the future object handling the result of the last asynchronous operation.
c10_16.png
One way to access the value from std::future is to use the .get member function. Unfortunately, this blocks the caller if the future isn’t finished. This is useful when you want to perform parallel computations and collect the results before continuing the program, but it’s a problem in interactive systems.
c10_17.png
Instead of blocking the execution of the program, it’s better to attach a continuation function to the future object. When the value becomes available, the continuation function will be called to process it.
c10_18.png

Summary

  • Programming patterns are usually connected to object-oriented programming, but the functional programming world is also filled with often-used idioms and abstractions such as the functor and monad.
  • Functors are collection-like structures that know how to apply a transformation function on their contents.
  • Monads know everything that functors do, but they have two additional operations: they let you create monadic values from normal values, and they can flatten out nested monadic values.
  • Functors allow you to easily handle and transform wrapper types, whereas monads let you compose functions that return wrapper types.
  • It’s often useful to think about monads as boxes. But use the term box loosely, to cover cases such as the continuation monad—a box that will eventually contain data.
  • You can open a box in the real world to see what’s inside, but this isn’t the case with monads. In the general case, you can only tell the box what to do with the value(s) it has—you can’t always access the value directly.

FAQ

What is a “functor” in this chapter, and what laws must transform obey?

Here, a functor is a type constructor F<T> that defines transform (also called map) to lift a function T1 → T2 so it operates on F<T1> and returns F<T2>.

Required laws for transform:

  • Identity: transform(f, id) == f
  • Composition: transform(transform(f, t1), t2) == transform(f, x → t2(t1(x)))

STL collections and ranges behave like functors (with the caveat that some range adaptors return a different range type).

Why aren’t functors enough when functions return wrapper types?

When your functions already return wrapped values (for example, std::optional<std::string>), transform nests wrappers: transforming std::optional<T> with T → std::optional<U> gives std::optional<std::optional<U>>. Repeated composition keeps increasing nesting, which is hard to work with. Monads fix this by providing a way to flatten one layer of wrapping while composing.

What is a monad, and how does it relate to functors?

A monad M provides two operations:

  • construct: T → M<T>
  • mbind: (M<T1>, T1 → M<T2>) → M<T2>

You can think of mbind as transform followed by a flatten (join: M<M<T>> → M<T>). Every monad is a functor because you can implement transform via mbind and construct. Monad laws (left identity, right identity, associativity) ensure predictable composition.

How do I use std::optional as a functor and a monad?

As a functor, transform applies a function to the contained value (if present) and wraps the result:

transform(optional<T>, T -> U) -> optional<U>

As a monad, mbind composes functions that return optionals and avoids nesting:

mbind(optional<T> opt, T -> optional<U> f) -> optional<U>
// if opt has a value, return f(value); otherwise return empty

Example chaining (pipe syntax shown):

current_login | mbind(user_full_name)
              | mbind(to_html)
How does expected<T,E> improve error handling compared to optional?

expected<T,E> carries either a value or an error E. Its mbind:

  • Propagates the first error without calling subsequent functions.
  • Calls f(value) only when the value is present.

This lets you chain operations that can fail and stop at the first error while preserving error details.

What does mbind do for collections and ranges, and how can it express filtering?

For collection-like monads (vectors, ranges), mbind is flatMap: map each element to a collection/range, then concatenate (join) the results. This allows generating zero, one, or many outputs per input.

Filtering can be expressed by returning an empty range for elements that don’t pass and a single-element range for those that do. This same foundation enables range/monad comprehensions (for_each + yield_if) that flatten nested loops while optionally filtering.

What’s the difference between join and mbind, and when should I use them?

join removes one layer of nesting: M<M<T>> → M<T>. mbind is a convenience that does transform followed by join in one step:

mbind(m, f) == join(transform(m, f))

In practice, prefer mbind for chaining functions that already return M<•>. Use join explicitly when you directly have nested monads and need to flatten them.

What is the Writer (with_log) monad and why would I use it?

The Writer pattern (with_log<T> in the chapter) carries a value together with accumulated context (for example, a debug log). mbind applies the function to the value and concatenates the new log with the existing one. It’s useful for structured logging or building state in a pure, composable way, including across asynchronous chains, without interleaving output.

Are futures monads? How do I chain asynchronous operations in C++?

Yes, futures can be modeled as monads: construct wraps an immediate value; transform maps the future’s value; mbind chains asynchronous operations that themselves return futures, avoiding future<future<T>> nesting.

Standard std::future blocks on .get; continuations require .then (available via std::experimental::future or boost::future). With then, mbind becomes trivial and you can write:

current_user() | mbind(user_full_name)
               | mbind(to_html)

This style is the continuation monad.

What is monad (Kleisli) composition and how does it help?

Kleisli composition composes monadic functions without mentioning the input value:

mcompose(f, g) = (x -> mbind(f(x), g))

It obeys identity and associativity (with construct as identity), making pipelines concise. Example: grandchildren = mcompose(children, children).

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Functional Programming in C++ ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Functional Programming in C++ ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Functional Programming in C++ ebook for free