Maybe monad


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

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()