# Monads in Functional Programming Explained

Monad is a generic concept that helps deal with side effects in doing operations between pure functions.

Written by Vidisha Jitani
Image: Shutterstock / Built In
UPDATED BY
Brennan Whitfield | Jul 20, 2023

A monad is a generic concept that helps streamline operations between pure functions in functional programming, often to handle side effects.

A monad is a generic structure in functional programming that handles side effects in pure functions. It provides a scalable approach for composing pure functions by using bind and unit concepts.

Here, I’ll explain the complex logic in simple words. Also, I’ll code in Python so that Haskell’s syntax doesn’t scare you away.

## Functional Programming and Monads Explained

Before starting, let me first introduce you to the main feature of Haskell — functional programming — which is the reason monads are needed in the first place. Functional programming is a mindset in which all design is thought of in terms of pure functions. There are two key concepts you need to understand:

• Functions are first-class citizens. So, all simple logic is a function, and all complex logic is handled by doing operations between functions.
• Functions have to be pure. This means whatever input you give, the output should always remain the same. The only way to interact with pure functions is via input and output only. It can’t access global states, print anything or even throw exceptions until defined in the function definition.

As I explained earlier, a monad is a general concept that helps with operations between pure functions to deal with side effects (which is when a function changes a variable that’s non-local or outside of its scope).

To help you understand what that means, I’ll explain what a monad is using an example, and it will become clear in a jiffy.

More on Python: 5 Types of Arguments in Python Function Definitions

## How a Monad Works in Functional Programming (With a Monad Example)

### Problem Statement

``````def square(num: int) -> int:
return num * num;
print(square(square(2)));
--------------------------------------------------------------------
Output
16``````

### Introducing Side Effects

Now, let’s add a side-effect to the function by printing the current value of input as well.

``````def sqaure_with_print(num: int) -> int:
print("Current num: ", num);
return num * num;
print(sqaure_with_print(sqaure_with_print(2)));
--------------------------------------------------------------------
Output
Current num:  2
Current num:  4
16``````

### Pure Functions With Side Effects

Due to the introduction of the print statement, the above function is no longer a pure function. What do we do now? How do we handle side-effects in a pure function? Remember, the only way to interact with a pure function is via input and output. We need to get the logs in the output itself.

``````def sqaure_with_print_return(num: int) -> (int, str):
logs = "Current num " + str(num);
return (num * num, logs);
print(sqaure_with_print_return(sqaure_with_print_return(2)));
--------------------------------------------------------------------
Output
Traceback (most recent call last):
File "hello.py", line 21, in <module>
print(sqaure_with_print_return(sqaure_with_print_return(2)));
File "hello.py", line 17, in sqaure_with_print_return
return (num * num, logs);
TypeError: can't multiply sequence by non-int of type 'tuple'``````

### Custom Composing

Oops. While we were able to make it a pure function, in order to do the chaining of the functions along with outputting, the side-effects broke the program. Why did this happen? The function was expecting `int` and we passed `(int, str)`. Looks like it was just an expectation mismatch. Looks like some special handling needs to be done.

We might need to modify `square_with_print_return`, such that it can accept `(int, str)` rather than `int`. Should we change the input signature of a function, just so that they can combine? Will that be scalable?

Instead, let’s add a custom compose function, which knows how to handle the above parameter mismatch.

``````def sqaure_with_print_return(num: int) -> (int, str):
logs = "Current num " + str(num);
return (num * num, logs);
def compose(func2, func1, num: int):
res1 = func1(num)
res2 = func2(res1[0])
return (res2[0], res1[1] + res2[1]);
print(compose(sqaure_with_print_return, sqaure_with_print_return, 2));
--------------------------------------------------------------------
Output
(16, 'Current num 2 Current num 4')``````

It worked. Now, let’s say we want to chain three functions. We’ll have to rewrite this compose function, right? We need to find a scalable way of writing this function so that it can handle any number of functions.

### Wrapping Pure Functions

A better solution is to use other wrapper functions, which can help with handling the input and output parameters in the desired format. This function will know how to handle side-effects without changing the pure function’s parameter.

``````from typing import Tuple
def sqaure_with_print_return(num: int) -> (int, str):
logs = "Current num " + str(num);
return (num * num, logs);
def bind(func, tuple: Tuple[int, str]):
res = func(tuple[0])
return (res[0], tuple[1] + res[1])
def unit(number: int):
return (number, "");
print(bind(sqaure_with_print_return, (bind(sqaure_with_print_return, unit(2)))))
--------------------------------------------------------------------
Output
(16, 'Current num 2 Current num 4')``````
• Bind function: This is a wrapper function on `square_with_print_return `that accepts a tuple, rather than just int. So, all such functions can be wrapped with a bind function. Because of this, now your pure function can handle any kind of input parameters.
• Unit function: Our first parameter was an integer. Someone should convert that to a tuple as well, right? That’s where the unit function helps.

And this, my friend, is a monad.

More on Python: Python Class Inheritance Explained

Monads allow users to simplify structure and control flows in functional programming, helping make code more declarative, type-safe and clear to follow. As monads are effective at isolating and containing side effects, this makes them useful for solving input/output (I/O) errors, parsing a program’s state and exception handling. They can also wrap values with extra data that would otherwise be inaccessible to functions, and facilitate calling functions under certain conditions.