Functional Programming in PHP: Part 5

bingo-functional artwork

Error handling can be a pain in the ass. The most common approach to error handling, using exceptions, is a staple of Object-Oriented Programming (OOP) codebases. The difficulty with using exceptions is that they are difficult to use when creating a coherent flow. Flow control constitutes the ethos of functional programming as it emphasizes a graceful, controlled transformation of state.

Exceptions, in my opinion, are impure because they can originate from sectors outside a current codebase. Also, exceptions, when caught in try-catch blocks can return values that are a far cry from the expected result of a function definition — they break referential transparency. The example below demonstrates both disadvantages of relying on exceptions.

<?php
function throwSomeException()
{
throw new Exception('I am an exception');
}
function addFunction(int $operand)
{
$otherOperand = throwSomeException();
try {
$result = $operand + $otherOperand;
} catch (Exception $excpetion) {
$result = 30;
}
return $result;
}
addFunction(12); 
//prints a PHP Warning
//the warning cannot be substituted with the intended result
//referential transparency red flag

Though in all fairness, there are arguments to be made for using exceptions, the alternatives which advance the idea of majestic code transformation should suffice. After all, flow control is central to the notion of reducing the cognitive burden. Enter the Maybe and Either monads, data structures that in each instance can contain one specified primitive. Commonly referred to as union types, Maybe and Either monads minimize the risk of pushing errors to the tail end of a program. The Maybe and Either monads are part of the bingo-functional library specification.

The Maybe monad can hold two values: a just value, the desired result of a computation, and a ‘nothing’ value, a null type. The nuance in the Either monad is present not only in the names of the encapsulated types (left and right) but also in the ability of the left value, to which an error is usually bound to hold an arbitrary value.

<?php
use Chemem\Bingo\Functional\Functors\{Either\Either, Maybe\Maybe};
$number = Maybe::fromValue(12);
$finalValue = $number
->filter(
function (int $number) : int {
return $number > 5;
}
)
->map(
function (int $number) : int {
return $number * 4;
}
);
var_dump($finalValue); //outputs 48 wrapped in Maybe\Just object

In the snippet above, the integer, 12, is sequentially transformed. First, the value is evaluated based on a boolean predicate — if it is greater than 5, it is multiplied by 4. The result of the computation, 48, is encapsulated in a Just type object. What happens to values less than 5? They evaluate to a Nothing type object containing a null value.

The Either monad, on the other hand, contains a Right type the contents of which are values which can be sequentially mutated, and a Left type which is a placeholder for arbitrarily defined error values. Usage of the Either monad is relatively similar to that of the Maybe monad.

$number = Either::right(12);
$finalValue = $number
->filter(
function (int $number) : int {
return $number > 5;
},
24 //arbitrary error value
)
->map(
function (int $number) : int {
return $number * 4;
}
);
var_dump($finalValue); 
//outputs 48 wrapped in an Either\Right object

In addition to chaining value transformations with Monads, it is possible to lift functions to make them accept Maybe or Either monad types as arguments. The beauty of using the lift function is that it multiplies the number of morphisms with which one can mutate state. The output of Lifted functions is, pretty much like the primitives used in the previous example, always encapsulated within a union type.

function add(int $x, int $y) : int
{
return $x + $y;
}
$maybeLifted = Maybe::lift('add');
$eitherLifted = Maybe::lift('add');
$maybeResult = $maybeLifted(
Maybe::fromValue(2),
Maybe::fromValue(3)
);
$eitherResult = $eitherLifted(
Either::right(2),
Either::right(3)
);
$maybeResult->flatMap('var_dump'); //outputs 5
$eitherResult->flatMap('var_dump'); //also outputs 5

There exists a genealogy of functions common to both Either and Maybe monads which can be used to enhance flow control. The full scope of the Either and Maybe monads is explained in the library documentation.

In conclusion, Monads provide a convenient, cognitive-burden reducing means of managing state mutations. Monads, in particular, the Either and Maybe monads, put programmers in a position to systematically move from one point to another. In the next article, a commentary on functors and an elucidation of IO, Writer, Reader, State, and List monads will feature.