The Maybe Monad in PHP

June 17, 2015

This week I was working on a web application that summarizes surveys into some simple statistics. My employer, a college, asks its students to evaluate their courses. For each course, a separate survey is created. The survey title holds a code which uniquely identifies the subject, period, year and location in which a course was taught. A survey title ends up looking something like:

Course evaluation (2BLZT1ZEV2014)

The external survey system exposes a rudimentary API using which a list of surveys can be requested. This API can only return all surveys in the system.
Our web application needs to locally filter which surveys are course evaluations and need to be analyzed. This problem certainly is no rocket science, and can be easily solved using the standard imperative programming toolbox. However, just having learnt how elegantly this problem can be solved using the appropriate monads, I was tempted to implement some of them and use in this situation.

Let’s start off by quickly documenting the steps required to identify and process course evaluations.

  1. Fetch a list of all surveys in the system
  2. Determine which surveys have a course evaluation code
  3. If it has a code, parse it to obtain the subject, period, year and location
  4. If the code was successfully parsed, analyze the survey.

Steps 2 and 3 can succeed or fail (sorry for playing Captain Obvious here). Step 2 fails if a survey title doesn’t follow the specified format; step 3 fails if the code is invalid. Successive steps depend on the successful completion of all previous steps, much like the assembly line in a car factory. Functional languages such as Scala and Haskell offer types that facilitate modelling these dependencies in code. These types are the Option (Scala) and Maybe (Haskell) monads respectively.

Instead of discussing the theoretical properties of monads, we will consider what behaviour we expect from our Maybe monad. This monad is a sort of container that can hold either 1 value, or nothing. Maybe (forgive the pun), it helps to think of it as an array that contains either 0 or 1 element (e.g., it can either contain a survey title or not). By storing a value, the Maybe monad signals that thus far every step in the assembly line was successful. If it doesn’t, it means one of the steps failed.

To further process the value inside the monad, Maybe objects should provide a method accepts a function as its argument. If Maybe contains a value, that value is passed to the function, which returns another Maybe. In PHP, we would create a class with a method, conventionally named bind, that does this. Let’s have a look at how this method would be implemented:

You see that if Maybe contains no value, it simply returns itself. There’s nothing to process, and we cannot create something out of nothing. Fortunately, $this already is a Maybe value that contains nothing and we can just return it directly. However, if Maybe does have a value, we pass it on to $function.

The most important requirement is that $function($this->value) returns another instance of Maybe. Its return value doesn’t necessarily need to hold a value, it could return nothing.

Think of a each bound function as a factory worker. Each worker has a task that can fail (i.e. return nothing). If the worker is successful the intermediate result is passed to the next worker (using bind). However, the entire production process is halted as soon as one of the workers fails to produce a result. In that case all workers next in line do nothing. The Maybe monad acts as a sort of coordinator. It stores an intermediate result (if any), and passes it on to the next worker in line.

To start using the bind method, we need to wrap a starting value in a Maybe first. In Haskell, each monad provides a function return, that takes a value and puts it into a monadic context. As return is a reserved keyword in PHP, I chose to name it pure. There are also methods to check if Maybe holds a value, called isJust, and to see if Maybe holds nothing, isNothing. Finally, there’s a method that returns the value from its monadic context that I call getJust:

Note that, it’s arguable whether having a value of null correspond to nothing makes sense. However, null is so often used to represent the concept of nothing in imperative programming languages that I opted for this solution.
Scala does something similar. It provides an Option class and a factory method Option(value: T) that returns the Nothing object if value == null and an object of class Some[T] if value != null. The static method pure acts much like Scala’s Option factory method.

Let’s see how this code might be used to solve our original problem:

The function codeFromTitle extracts the code from $title but returns null if no code was found. Maybe::pure puts the result in a monadic context. If a code was returned, it’s then parsed. The parseCode function returns a ParsedSurveyCode object if successful or null. If the code could be parsed, the survey is processed.

Equivalent, imperative code would be:

That code is probably more familiar to PHP programmers.

The cumbersome (absent?) support for functional programming techniques in PHP, probably doesn’t make for very pretty, legible, maintainable or more safe code when using monads. There are no compile time checks in place to prevent bound functions from escaping the monadic context. Anonymous functions, as passed to the bind method, are not really closures, and to use variables from the lexical scope you have to explicitly copy them (either by value or reference) to the scope of the anonymous function.

In more strongly typed languages you could use a Maybe value as an extra safeguard against null pointer exceptions or let your programs work in a context of potential failure or uncertainty. But that’s for another blog post.

(I uploaded my implementation of the Maybe monad in PHP to Github. The repository also includes an implementation of Either, which allows you to return a reason of failure instead of a less descriptive nothing. You can find the code in this Github repository.)