Understanding Monads

Lesson Introduction

Monads can make your code much cleaner and safer, especially for handling errors and chaining operations. Today, we aim to understand what a monad is, specifically the Maybe monad, and see how it helps in functional programming. By the end of this lesson, you'll know how to create and use a Maybe monad and how to chain operations using the bind method.

What is a Monad?

A monad is a design pattern used in functional programming to handle program logic that involves wrapping a value, performing operations, and managing side effects. Monad is an extension of Functor. Effectively, Monad does the same thing as the Functor, but it provides additional logic to handle all possible scenarios like data of incorrect type or None instead of value.

Creating the Maybe Monad

The Maybe monad represents values that might or might not exist. It helps avoid errors when you try to use missing values. Let's create the Maybe monad in Python step-by-step:

from typing import Optional, TypeVar, Generic

T = TypeVar('T')

class Maybe(Generic[T]):
    def __init__(self, value: Optional[T] = None):
        self._value = value

This constructor holds a value that can either be None (absence of value) or any other type T.

Checking State in Maybe Monad

To work with our Maybe monad, we check if it contains a value or not. We do this using the is_just method.

from typing import Optional, TypeVar, Generic

T = TypeVar('T')

class Maybe(Generic[T]):
    def __init__(self, value: Optional[T] = None):
        self._value = value

    def is_just(self) -> bool:
        return not self.is_nothing()

is_just checks if the Maybe monad contains a value.

Binding Functions with Maybe Monad

Next, we implement the map method, similar to the one we had in functors.

from typing import Callable
from typing import Optional, TypeVar, Generic

T = TypeVar('T')
U = TypeVar('U')


class Maybe(Generic[T]):
    def __init__(self, value: Optional[T] = None):
        self._value = value

    def is_just(self) -> bool:
        return self._value is not None

    def map(self, f: Callable[[T], U]) -> "Maybe[U]":
        if self.is_just():
            return Maybe(f(self._value))
        return Maybe()

It works simply. First, we check if our monad has a value. If it does, we apply the given function to it and wrap the result back into monad. Otherwise, we return a new empty monad. So far, monad works in the same manner as a functor, but there is a problem with this approach. Let's explore it.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal