Lambda To The Rescue: Lists to wrap failure

This is the first post in a series about using Functional Programming concepts to make your Object Oriented code more comprehensible. Hang tight for more.

Readability

When we're reading code, one of the most important blocking factors is cognitive overhead. Minimize it, and your code instantly becomes easier to read and understand. Or is it the other way 'round? After some years of playing with functional programming languages in my spare time, I saw some things that functional programming does great in this field. One of them involves lists.

As you might know, lists are among the most important datastructures in functional programming, if not the most important. There's even a whole family of programming languages that took their name from them. Looking at you Lisp 😘!

Let's dive in with a typical legacy PHP method discovered in the wild:

<?php

function words($string)
{
    if (empty($string)) {
        return false;
    }

    return explode(' ', $string);
}

var_dump(words('')); // returns false
var_dump(words('foo bar baz')); // returns ['foo', 'bar', 'baz'];

The reason the empty check is there, apparently, is that explode(' ', $s) with an empty string will return [''] a list with an empty string in it, which is really strange and non-desirable. Arguably, if the $string variable can't be empty, we should throw an exception. But let's say we want to explore our options here. Before I look into the body of a function, I always try to understand it as a black blox.

If I pass words a string, it returns an array of words in the string. Except that for empty strings, it will return false.

Now, the "black box" already has some mystery to it. Why does it return false? Let's defer the answer to it, and check out if we can write the PHP7 type annotations for it.

function words(string $string): array
{
    // ...
}

Now, this is not correct. The exception that is made for empty strings can't be written as a type. In old-skool PHP they would've written a DocBlock like this:

/**
 * @param string $string
 * @return array|false depending on failure
 */
function words($string)
{
    // ...
}

But that is a type signature that is unrepresentable usinig PHP7 types. What if we would represent failure using an empty array?

function words(string $string): array
{
    if (empty($string)) {
        return array();
    }

    return explode(' ', $string);
}

Now the type signature just works. We turned "failure" into a valid result that we can work with. For instance, all code that works with the return value of this method can now just work with arrays. The type signature now tells a story about the function too:

If I pass words a string, it returns an array of words in the string.

The base case where the string is empty is now logical: an empty string has no words, so a list of 0 words is returned.

Chaining calls

Now, let's say we have a function chars that returns a list of chars in a string:

function chars(string $string): array
{
    if (empty($string)) {
        return array();
    }

    return str_split($string);
}

We're thinking we should be able to use this to list all the characters that appear within words. Would this work?

chars(words('foo bar baz'));

No... We get a fatal error Fatal error: Uncaught TypeError: Argument 1 passed to chars() must be of the type string, array given. Of course, the result of words is an array, which cannot be used as input to our chars function. Let's use array_map:

array_map(@chars, words('foo bar baz'));

don't worry about the @, it's a trick that I learned while working on lambdalicious. @ is PHP's error suppressing operator. In this case it allows us to write a function's name as a string, without using string notation, so that it's visually different from a string, BUT is interpreted by PHP as a string...

We get this:

[["f","o","o"],["b","a","r"],["b","a","z"]]

Of course, after mapping chars over the words, this is what we get... But actually, we wanted this:

["f","o","o","b","a","r","b","a","z"]

So how do we get there? We "flatten" the arrays by using concat, which does not exist in php. Let's write it ourselves:

function array_concat(array $arrays): array
{
    return call_user_func_array(
        @array_merge,
        array_merge(array(array()), $arrays)
    );
}

Now we can use it to flatten the array:

array_concat(array_map(@chars, words('foo bar baz')));

TADAAAA 😆 We get what we wanted!

["f","o","o","b","a","r","b","a","z"]

Let's call this combination of array_map and array_concat array_bind for now:

function array_bind(array $array, $f)
{
    return array_concat(array_map($f, $array));
}

array_bind takes an array of values, and a function that operates of one of those values and returns an array. It returns a new array of values.

array_bind(array_bind(array('foo bar baz'), @words), @chars);
// returns ["f","o","o","b","a","r","b","a","z"]

Now that we have the array_bind function, we can apply functions to something in an array, and the result will always be an array! What happens if we first bind words and then bind chars to an empty string?

array_bind(array_bind(array(''), @words), @chars); // returns []

We get an empty array back, which is great!

You saw me wrap the empty string in an array, to be able to use array_bind. This is an act of "providing context". The array serves as a context wrapper for results of the chained functions words and chars. We'll always get our result as an array. If anywhere in the process something fails, we get an empty array, otherwise we get an array with value(s) in it.

I must admit, this doesn't look really great. Let's take a look at how this works in Haskell, a pure functional language.

Now, this is the equivalent of what we wrote earlier on in PHP:

pure "" >>= words >>= chars
pure "foo bar baz" >>= words >>= chars

These return [] and ["f","o","o","b","a","r","b","a","z"], as expected, but how?

  • pure wraps the string in our "wrapper" list.
  • >>= is the bind infix function; it takes the "wrapped" value on the left, and applies the function on the right to the wrapped value. It then returns a new wrapped value.
  • words takes a String and returns a list of strings [String], split by spaces. It's in Haskell's default module Prelude.
  • chars is a function we made up, it takes a String and returns a list of strings [String]. By default, Strings in Haskell are lists of Chars. To break up a given string in a list of strings that represent chars we need to map over the input string. The mapping function (\x -> [x]) gets a Char as input and returns a String by wrapping the given Char in a list; check it out:

    chars :: String -> [String]
    chars s = map (\x -> [x]) s
    

As you can see, Haskell was made for this kind of stuff... It also reads a lot better than our PHP implementation.

Expanding on this concept

The reason this doesn't really work in PHP is that we don't have infix operators at our disposal. I've seen libraries that do something with method calls along these lines:

pure('foo bar baz')->bind(@words)->bind(@chars);

which already looks a lot better than what we came up with. Failure can be encapsulated. There's a lot less boilerplate code to read. There's no checking for empty arrays or failure, while it still behaves as we wanted.

  • pure() wraps the value in an object.
  • bind() is equivalent to returning a new wrapper object with the result of the applied function to the previously wrapped value. Internally it could all work using arrays, but it could just as well be done differently.

For now, we've always worked with functions that return a number of results, but this whole concept can just as well be used with functions that are supposed to return a single result, like integer division div:

function div(int $divident, int $divisor): WrappedValue
{
    if ($divisor === 0) {
        return nothing();
    }

    return pure(intdiv($divident, $divisor));
}

Now, we created an integer division function that doesn't throw DivisionByZeroError exceptions, but uses this system of WrappedValues to wrap it's return value on success (using pure, equivalent to an array with a value) or on failure (using nothing, equivalent to an empty array). Now, if we want to square the result of this, we can just bind square to the result of the previous computation.

function square(int $number): WrappedValue
{
    return pure($number * $number);
}

var_dump(div(10, 2)->bind(@square)); // [25]
var_dump(div(10, 0)->bind(@square)); // []

As with the previous examples, we can now just focus on the order of computation, without the need to worry about failure in the in-between steps.

There are also ways to make this whole thing even more interesting by providing more info like error messages in case of failure.

Day-to-day usage

I'm not saying this is perfect for everything in OOP by any means, but we might want to think about how we handle failure in our (legacy) systems. I sometimes see trees of exception handling and boolean/null-checks on return values. This is a third option (PHP doesn't have option types by default); In lots of ways, the classic Null Object pattern resembles this whole thing in a more Object Oriented way. In many functional programming languages this is actually the most widely used means of error handing. If you want to know more about it, I suggest reading up on Functors, Applicatives, and Monads. Scary names for new concepts, don't let them hold you back.

If you want to do the most simple and useful thing: think about how your functions behave. What goes in, what comes out? Writing the type annotations helps a lot. And of course, throwing exceptions is not a sin.

Happy programming y'aλλ! 👋

What's next?

Upcoming lambda to the rescue posts will be linked here:

Categories: Functional Programming