Monads in Chains
Monads are scary, and monads are evil. But they are still useful.
In the recent years, the abuse of multi-threading has become apparent and more and more systems started being developed in the reactive, or event-processing style. It allows lowering the number of threads to match the system cores instead of items that are being processed by using non-blocking asynchronous APIs.
The downside is that the code becomes difficult to comprehend and maintain due to the inversion of control (IoC). A number of approaches have been created to ease the reactive development (message passing and actors as an prominent example coming from the world of Erlang) but the most used approach is still based on call-callbacks.
We are presenting a way of defining a set of abstractions based on monads which allows the user to define the algorithm flow in a procedural manner, while leaving it up to the C++ compiler to generate the necessary asynchronous code.
We will show how to leverage the modern C++ features like std::future and others to implement the continuation monad in a completely generic way.
With the continuators and continuations, we are able to mimic all control structures provided by the language like serial execution, different loop types, branching and similar, along with some that are not available such as the parallel execution that can wait for all subtasks to end (all_of), for only one (any_of), or just detach the tasks and ignore whether they have finished or not; transactional execution which undos all the steps in reverse if some of them failed and similar. We could even pull off some idioms from other programming languages like do or die("error message") in Perl and PHP.
By converting any type of asynchronous (threads, fibers, event-loop-based, GPU calculations etc.) or synchronous execution to a continuation, we get a very powerful abstraction that allows us to treat them all the same. And all that without any performance penalties thanks to the template unwrapping, inlining and other optimizations done by the compiler.
This approach eases the development in many cases such as implementing the networking clients or servers where we are not allowed to block the program execution between sending a request and receiving a response; programs that use a limited thread pool or thread-reusing with the producer-consumer design pattern; programs that need to invoke an external process and handle its output data in chunks or as a whole; programs that have more complex user (UI) flows and similar.
Speaker: Ivan Cukic
Slides: Monads in Chains