|
gimo v0.1.0
|
Generic Interchangeable Monadic Operations
gimo is a small C++20 library that provides reusable monadic operations as free functions. Although C++23 introduces monadic operations for std::optional (and std::expected) as member functions, their real-world usability is still quite limited.
In the following sections, I’ll explain the general idea behind gimo.
The C++ standard library is well known for its generic algorithms, which decouple concrete container implementations from the operations performed on them. For example, there is no std::vector::find, because std::find (and, since C++20, std::ranges::find) already solves the problem generically.
There is no need to reimplement the same algorithm for each container if the actual goal is simply to perform a linear search. Doing so would only result in more code, without any real benefit.
This is exactly what I’d like to have for monadic operations. Therefore, gimo provides the following algorithms for all nullable types out of the box:
Additionally, for expected-like types, gimo offers gimo::transform_error.
Providing these operations as free functions also enables customization. Users can add their own algorithms where needed, without being constrained by member functions.
C++ has a long history, and many codebases already use their own optional- or expected-like types. Each of these typically has to implement monadic operations on its own. Even when such operations exist, it is currently impossible to mix different vocabulary types within a single pipeline.
gimo makes it possible to build pipelines that involve different closure types. Consider a case where we want to use both std::optional and nonstd-lite/optional-lite in the same pipeline. Assuming that integration for both types is in place, the following just works:
See the full example on godbolt.org.
This section describes more of a nice side effect, but it’s something I like to point out in discussions, because it supports the idea that monadic operations should be free functions.
Consider the following snippet, where several transform operations are composed in a single pipeline:
The final result is not particularly surprising. However, if we look a bit closer at what happens under the hood, some inefficiencies become apparent.
We start with a non-empty std::optional and call transform. This operation checks whether the optional contains a value and then applies the function. The second transform has to perform the same emptiness check again, even though the first transform can never change that invariant. Each operation is fully isolated, and no information is propagated between them.
Now let’s look at the same example using gimo:
Here, the first transform still needs to evaluate emptiness. However, once that information is known, it can be forwarded to the next operation.
In fact, this closely resembles the following rewritten code, which users may have in mind when constructing the pipeline.
Note that these optimizations depend on the specific algorithm, as each has different semantic properties. For instance, or_else preserves the emptiness state when the nullable holds a value, allowing this information to be propagated. Since the fallback action is not guaranteed to produce a non-empty nullable, the next operation must still perform its own check.
gimo is a header-only library, allowing users to easily access all features by simply including the gimo.hpp header.
The gimo core is a type-agnostic framework for free-standing monadic operations. Support for specific types is available via an opt-in model through the gimo_ext directory. These headers provide the necessary trait specializations to adapt existing types to the gimo pipeline. For instance, to enable support for std::optional, simply include the corresponding adapter:
Currently, the following extensions are provided:
The gimo framework is architected for broad compatibility with any C++20-conforming compiler, maintaining strict independence from underlying hardware architectures or operating systems. The library has been verified across various environments, including Windows, macOS, and major Linux distributions (Ubuntu, Debian) on both x86_64 and x86_32 platforms.
The library is verified to work with the following compiler versions. While older versions supporting C++20 may work, these represent the minimums confirmed by the current test suite:
The integration into a CMake project is straight-forward.
Users can either select a commit in the main branch or a version tag and utilize the CMake FetchContent module:
As an alternative, I recommend using CPM, which is a convenient wrapper based on the FetchContent feature:
For convenience, an amalgamated version is available via gimo-amalgamate.hpp. This file tracks the current state of the main branch as a single, self-contained header. Please note that extensions found in gimo_ext are excluded from this file and must be included separately.