Overview

12 Functional design for concurrent systems

This chapter addresses the core challenge of taming complexity in concurrent software by designing systems as sets of isolated, message-driven components. It advocates the actor mindset—components share nothing and communicate via messages—and reframes those messages as reactive streams that can be transformed and composed functionally. By focusing on what components do (their behaviors) rather than what data they expose, the design eliminates shared mutable state and the synchronization it demands, making systems easier to reason about, safer, and more scalable.

The chapter develops a practical, typed-actor toolkit and demonstrates it through a small bookmark-processing service. It introduces sources, sinks, and general actors, then builds a stream pipeline with range-like, pipeable operators (transform, filter, join). Reactive streams are treated as monads: transform lifts functions over streams, values constructs streams from constants, and join flattens streams of streams. Errors are handled functionally by converting exceptions into values (expected) and composing with mbind, preserving error information throughout. To support replies while keeping the pipeline decoupled, messages are wrapped in a with_client context that carries the client handle alongside evolving payloads; lifting utilities adapt ordinary functions to operate transparently over this nested, monadic structure.

While promoting immutability, the chapter also shows where stateful actors are appropriate (for example, join keeping active sources, or throttling to mitigate abusive clients). Because each actor processes messages serially and shares no state, such mutability requires no locks. Crucially, the functional, message-centric design decouples logic from infrastructure: moving from single-threaded to multithreaded or distributed execution chiefly changes message delivery (queues, scheduling, serialization), not the pipeline itself. The result is a composable, testable, and resilient approach to concurrent and distributed system design grounded in actors and reactive streams.

We can’t set the attributes of real-life objects. We can send them messages and let the objects react to those messages.
c12_01.png
An actor is an isolated component that can receive and send messages. It processes the messages serially; for each message, it can change its state or behavior, or it can send a new message to another actor in the system.
c12_02.png
You’ll use simplified typed actors that don’t need to care about who sends the message to whom: that will be left to an external controller.
c12_03.png
The service actor listens for client connections and for their messages. This part is hidden from the rest of the program, which just knows a stream of strings is arriving from somewhere.
c12_04.png
You capture the shared pointer to the session object in the lambda that will process when new data arrives from the client. The session object will be kept alive as long as the client keeps the connection alive.
c12_05.png
Unlike the continuation monad, which calls the continuation function only once, reactive streams can have an arbitrary number of values. The continuation function is called for each new value that arrives.
c12_06.png
The sink actor calls a function for each message it receives. It emits nothing. You can use it to print out all the messages you receive to std::cerr or do something more advanced such as saving the messages to a file or a database.
c12_07.png
Similar to ranges, transform on reactive streams applies the given transformation function on each message it receives, and it sends the function result to the next actor.
c12_08.png

Summary

  • Most C++ programmers write procedural code. I recommend reading Object Thinking by David West (Microsoft Press, 2004) to help you start writing better object-oriented code. It’s beneficial even when you’re doing functional programming.
  • Humans tend to achieve grand things when they talk to one another. We don’t have a shared mind, but the ability to communicate helps us achieve complex goals. This is the reasoning that led to the invention of the actor model.
  • Monads can cooperate well. You shouldn’t be afraid to stack them on each other.
  • You can implement transformations for reactive streams similar to those for input ranges. You can’t implement things such as sorting on them, because for sorting you need random access to all elements, and you don’t even know how many elements a reactive stream will have—they’re potentially infinite.
  • Just like futures, common implementations of reactive streams aren’t limited to sending values; they can also send special messages such as “stream ended.” This can be useful for more efficient memory handling: you can destroy a stream when you know it won’t send you any more messages.

FAQ

What core problem does this chapter tackle in concurrent software?It addresses the explosion of complexity that arises from entangled components and shared mutable state, especially under concurrency. The chapter’s answer is to split systems into isolated components (actors) that communicate by sending messages, which reduces synchronization needs and makes behavior easier to reason about and compose.
How does the chapter’s actor model differ from a traditional actor framework?The chapter uses simplified, typed actors: each actor receives one message type and emits one (not necessarily the same) message type. Routing and scheduling are delegated to an external controller, which enables functional composition. Traditional actors often support multiple message types and choose recipients themselves; the chapter’s design prioritizes composability over feature breadth.
What are sources, sinks, and general actors?- Sources only emit messages (no input).
- Sinks only receive messages (no output).
- General actors both receive and emit. Actors expose a way to set a message handler and a process function for incoming messages, keep their internal state private, and avoid shared data, thereby eliminating the need for locks.
How is a simple network message source implemented with Boost.Asio?A service accepts TCP clients (async_accept), creates a session per connection, and reads line-delimited messages (async_read_until). Each session forwards received lines to the service’s emit function, making the rest of the system see a stream of strings without caring about sockets or clients.
What are reactive streams and why model them as monads?Reactive streams are sequences of values arriving over time (like collections, but time-based). Modeling them as monads gives uniform, composable operations: a constructor (e.g., values{...}), transform (map over messages), and join (flatten a stream of streams). This unifies design and simplifies building pipelines.
How do transform and filter work on reactive streams?transform applies a function to each incoming message and emits the result; filter forwards only messages that satisfy a predicate. With pipe syntax (operator|), you can build readable pipelines, e.g., service(event_loop) | transform(trim) | filter(predicate) | sink(...).
How is error handling done when parsing JSON messages?Use expected together with mtry to wrap exception-throwing code. Parse strings into expected, then either filter valid results and unwrap (get), or compose fallible steps with mbind to avoid verbose transform–filter–transform chains while preserving error information.
How can the server reply to the correct client through the pipeline?Wrap messages in with_client, which carries the value plus a socket pointer and a reply() helper. Then lift your existing transformations (lift_with_client/apply_with_client) so the pipeline logic stays the same; the client context travels alongside values, allowing the sink to send acknowledgments or errors back.
When are stateful actors appropriate, and how is state safe here?They’re useful for behaviors like throttling (e.g., per-client cooldowns) or join, which tracks multiple sources. State remains safe because each actor is isolated and processes messages serially, so no shared mutable data crosses actor boundaries and no locks are needed.
How does this design ease scaling to multithreaded and distributed systems?Because components are isolated and communicate via messages, only the delivery mechanism changes when moving to threads or processes—or across machines. Each actor has a queue and preserves message order; serialization and routing can be added without altering the core pipeline logic.

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