Lesson 4

*Monads* are a powerful concept in functional programming that helps manage side effects and represent computations as chains of steps. When you encounter situations where functions may fail or return optional values, Monads provide a way to handle these cases elegantly. In this lesson, our goal is to understand what Monads are, how they work, and how to implement and use them in C++ to manage `std::optional`

values safely and predictably.

Consider a situation where you have a mock user database, and your task is to retrieve user information and then transform it into HTML format. Handling the absence of users or the failure of any step is crucial. Here's the example code that sets up our data and declares functions we want to implement.

C++`1#include <iostream> 2#include <optional> 3#include <string> 4#include <unordered_map> 5 6// Mock database 7std::unordered_map<std::string, std::string> userDB = { 8 {"user1", "John Doe"}, 9 {"user2", "Jane Smith"} 10}; 11 12// Function to get user's full name 13std::optional<std::string> user_full_name(const std::string& login); 14 15// Function to convert text to HTML 16std::optional<std::string> to_html(const std::string& text); 17 18int main() { 19 20}`

Normally, we would approach this task with a functor from the previous lesson to handle optional values, but there is a problem: a functor wraps its answer into `std::optional`

. If the answer is **already** an `std::optional`

, we will get a nested optional structure, which is highly difficult to deal with.

In this lesson, we will implement a new version of our Functor – a Monad – which solves this problem.

A *Monad* is an abstraction that allows structuring programs generically. In our context, a Monad provides a way to sequence operations while handling potential failures gracefully. A Monad extends the functor's structure by adding a new method, called `join`

.

Let's create a Monad:

C++`1template<typename T> 2class Monad { 3public: 4 template<typename Func> 5 auto transform(const std::optional<T>& value, Func func) -> std::optional<decltype(func(*value))> { 6 if (value) { 7 return func(*value); 8 } else { 9 return std::nullopt; 10 } 11 } 12 13 std::optional<T> join(const std::optional<std::optional<T>>& nestedValue) { 14 if (nestedValue) { 15 return *nestedValue; 16 } else { 17 return std::nullopt; 18 } 19 } 20};`

It has a `transform`

method, implemented in the same manner as in the Functor from the previous lesson. But we can't just use this method with our functions `user_full_name`

and `to_html`

, as they will return optional values, which will be additionally wrapped in `optional`

by `transform`

.

To address this, we provide a new method, called `join`

. It is very simple – it just unwraps the optional, returning its value.

Now, let's implement function we are going to use.

The `user_full_name`

function retrieves the full name of a user based on their login:

C++`1std::optional<std::string> user_full_name(const std::string& login) { 2 auto it = userDB.find(login); 3 if (it != userDB.end()) { 4 return it->second; 5 } else { 6 return std::nullopt; 7 } 8}`

The `to_html`

function converts the text to HTML format:

C++`1std::optional<std::string> to_html(const std::string& text) { 2 return "<h1>" + text + "</h1>"; 3}`

These functions are quite simple. Note that they are defined to return an optional to make the program more flexable and safe.

To use the Monad, create an instance and utilize `join(transform())`

to chain operations:

C++`1int main() { 2 Monad<std::string> monad; 3 std::optional<std::string> current_login = "user1"; 4 5 std::optional<std::string> result = monad.join( 6 monad.transform( 7 monad.join( 8 monad.transform(current_login, user_full_name) 9 ), to_html) 10 ); 11 12 if (result) { 13 std::cout << "HTML result: " << *result << std::endl; // HTML result: <h1>John Doe</h1> 14 } else { 15 std::cout << "No result found." << std::endl; // No result found. 16 } 17 18 return 0; 19}`

You first retrieve the user's full name and then convert it to HTML, handling possible failures at each step.

In this lesson, we explored *Monads* and their significance in functional programming. We implemented a simple Monad in C++ and used it with `std::optional`

to chain operations safely. Monads help manage computations involving optional values, leading to cleaner and more predictable code.

Now, let's move on to hands-on practice tasks to apply the concepts learned. You'll implement Monads to handle various operations and practice chaining functions with optional values. This practice is crucial for solidifying your understanding of Monads in real-world scenarios.