Maybe monad


Martin McBride, 2020-07-02
Tags monad maybe monad just monad nothing monad design pattern
Categories functional programming

We looked at the Failure monad in an earlier article, It is worth reading that first if you haven't already, because this article builds on it.

The Failure monad allows us to deal with functions that might fail when they are called. We wrap values in a monad that contains a flag that indicates if the value is invalid because a function threw an exception while trying to do a calculation. The monad catches exceptions, marks the value as failed, and then doesn't call any other functions that use the value, it just marks the result as failed. The failure propagates back to the original caller, which means exceptions can be handled within the normal code path.

In this article we will look at a similar case - optional values - but take a slightly different approach, using two monads.

Optional values

It is not uncommon for programs to encounter values that may or may not be present at runtime. For example, your program might look up a value in a set of user preferences, or try to read a record from a database, and find out that in fact the value simply isn't there.

A traditional way to handle this situation would be to set the value of the variable to None if it can't be found. That kind of works but it has problems:

  • Your code base will be littered with if not x type statements to take special action if the values isn't present.
  • None doesn't specifically tell you that the value isn't present. Sometimes it is used because an error occurred in a calculation, or for other reasons.
  • Sometimes None is a valid option for a field, and doesn't indicate anything special such as a not present state.

This is quite similar to the Failure monad case. The main difference is that a function can potentially fail (as in throw an exception) at anytime, whereas if an optional value is unavailable we will know this right from the start. So we will handle it slightly differently. We will use a Maybe monad that has two separate sub-classes - Just and Nothing.

The Maybe monad

The Maybe monad often exists as an abstract class, with two concrete sub-classes, Just and Nothing. So when we talk about a Maybe object, what we actually mean is an object that could be either Just or Nothing.

In Python, because we can use duck typing, we don't necessarily have to define a Maybe class at all. So long as Just and Nothing implement all the required methods, they don't really need to be related by an actual base class. We will take that approach here.

The Just monad

The Just monad is quite similar to the Failure monad, in that it wraps a value, binds functions to that value. But that is all it needs to do. If an object has a value we will assume that any function will also return a value.

class Just():

    def __init__(self, value):
        self.value = value

    def get(self):
        return self.value

    def bind(self, f):
        result = f(self.value)
        return Just(result)

    def __str__(self):
        return 'Just(' + str(self.value) + ')'

    def __or__(self, f):
        return self.bind(f)

The Nothing monad

The Nothing monad represents no value. When we apply a function to Nothing (using bind), we don't actually call the function, because there is no value to pass to it. The result is always Nothing

class Nothing():

    def __init__(self):
        pass

    def get(self):
        return None

    def bind(self, f):
        return Nothing()

    def __str__(self):
        return 'Nothing()'

    def __or__(self, f):
        return self.bind(f)

Result

Here is the result of binding the neg function to a value Just(3) and ot no value Nothing:

value = Just(3)
result = value.bind(neg)
print(result)         # Just(-3)

no_value = Nothing()
result = no_value.bind(neg)
print(result)         # Nothing()
If you found this article useful, you might be interested in the book Functional Programming in Python or other books by the same author.

Prev

Popular tags

2d arrays abstract data type alignment and animation arc array arrays behavioural pattern bezier curve built-in function callable object chain circle classes close closure cmyk colour combinations comparison operator comprehension context context manager conversion count creational pattern data types design pattern device space dictionary drawing duck typing efficiency ellipse else encryption enumerate fill filter font font style for loop function function composition function plot functools game development generativepy tutorial generator geometry gif gradient greyscale higher order function hsl html image image processing imagesurface immutable object index inner function input installing iter iterable iterator itertools l system lambda function len line linear gradient linspace list list comprehension logical operator lru_cache magic method mandelbrot mandelbrot set map monad mutability named parameter numeric python numpy object open operator optional parameter or partial application path pattern permutations polygon positional parameter print pure function python standard library radial gradient range recipes rectangle recursion reduce repeat rgb rotation scaling sector segment sequence setup shape singleton slice slicing sound spirograph sprite square str stream string stroke structural pattern subpath symmetric encryption template text text metrics tinkerbell fractal transform translation transparency triangle tuple turtle unpacking user space vectorisation webserver website while loop zip