Maybe monad


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

This article is part of a series on 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()

See also

If you found this article useful you might be interested in my ebook Functional Programming in Python.


Tag cloud

2d arrays abstract data type alignment and array arrays bezier curve built-in function close closure colour comparison operator comprehension context conversion data types design pattern device space dictionary duck typing efficiency encryption enumerate filter font font style for loop function function plot functools generator gif gradient html image processing imagesurface immutable object index input installing iter iterator itertools lambda function len linspace list list comprehension logical operator lru_cache mandelbrot map monad mutability named parameter numeric python numpy object open operator optional parameter or path positional parameter print pure function radial gradient range recursion reduce rotation scaling sequence slice slicing sound spirograph str stream string subpath symmetric encryption template text text metrics transform translation transparency tuple unpacking user space vectorisation webserver website while loop zip

Copyright (c) Axlesoft Ltd 2020