A while ago I caught the functional bug. This is an irresistible urge to write code that conforms to functional principles: mathematical concepts ported into programming that open up new patterns because of the assumptions that are upheld. Essentially it’s a way of doing things that makes it cleaner to program in, makes debugging easier, and generally produces higher quality code.
Monads one small piece of this big picture and a very common one people use is called the Maybe monad. With Maybe you can repeatedly apply functions to a value if and only if it isn’t already nothing. Basically it allows the programmer to shift or remove a bunch of needless error handling.
// No Maybe, no happy
const getTotalCost = products => products.reduce( ( total, product ) => {
let sum = total;
if ( ! product.cost ) {
return sum;
}
sum += calculateCost( product.cost );
if ( ! ( product.labor && product.labor.cost ) ) {
return sum;
}
sum += calculateCost( product.labor.cost );
return sum;
}, 0 )
// Maybe a little bit happier
const getTotalCost = products => products.reduce( ( total, product ) => {
let p = Maybe( product ),
add = ( sum, m ) => ( m instanceof Nothing ) ? sum : sum + m.value;
let pCost = p.bind( p => p.cost ).bind( calculateCost );
let lCost = p.bind( p => p.labor ).bind( l => l.cost ).bind( calculateCost );
return [ pCost, lCost ].reduce( add, total );
}, 0 );
While maybe not the greatest example in the world, you can see how it linearized the flow of the function and we were able to remove all of those conditional statements. There are way better examples of this, but the gist is that we can safely compute the labor cost for each item because calculateCost
won’t be called for it if there are no associated labor costs in the object.
This isn’t the monad I created.
It’s not even the language I created it in.
I used PHP because I like pain that’s the language of WordPress. How can we bring functional programming patterns to PHP? That’s been the question I’ve been trying to answer.
My first monad handles a familiar PHP pattern to anyone who needs to spit out some text to the user with optional fallbacks in case that string is blank.
// Not too bad, but a little verbose - this could easily get out of hand
function get_title( $blog_id, $fallback ) {
$title = get_blog_option( $blog_id, 'blogname' );
if ( empty( trim( $title ) ) ) {
$title = $fallback;
}
if ( empty( trim( $title ) ) ) {
$title = get_blog_option( $blog_id, 'siteurl' );
}
if ( empty( trim( $title ) ) ) {
return null;
}
return sanitize( $title );
}
I realized that much of the code I write simply checks that a variable matches a certain condition and then sets it to a new value if it doesn’t, so I decided to create what I am calling the Until monad. It behaves similarly to Maybe, but takes a test function and will continue to process until that test function resolves true.
Monads normally bind functions, but this is PHP where function calls are expensive and the syntax for creating anonymous functions and closures is exhausting. Therefore I have cheated a little bit and allow plain old variables to be passed into the bind method. Let’s explain by illustration:
// Pretty straightforward and concise!
function get_title( $blog_id, $fallback = null ) {
return (new UntilText())
->surely( get_blog_option( $blog_id, 'blogname' ) )
->surely( $fallback )
->surely( get_blog_option( $blog_id, 'siteurl' ) )
->bind( 'sanitize' )
->extract();
}
There are several things going on here. First of all, I have created a subclass of the Until maybe specifically for texts. It makes sure that we have something that isn’t just a blank. We could continue to develop this subclass to make sure that we don’t get strings that are just blank Unicode characters as well.
class UntilText extends Until {
public function __construct( $value = null ) {
parent::__construct( function( $t ) {
return is_string( $t ) && ! empty( trim( $t ) );
}, $value );
}
}
Here you can see the test function – it’s just a plain old function that returns a boolean value. The Until itself looks like this (in pseudocode to spare you the hassle):
class Until extends Maybe:
construct( test, value )
surely( updater, args ):
if test( value ) is false:
newValue = updater or updater( value, args )
if test( newValue ) works:
return unit( newValue )
return unit( value )
extract():
if not test( value ):
return Nothing
return unit( value )
Why surely? Well, I couldn’t find a good antonym for maybe, so I though, “surely if it hasn’t met the test yet, it will after I update it with this!” This is special because it holds the original passed value until the test succeeds. Note that we can still pass around our good friend, the Maybe bind, offering us some pretty complicated chains. One more example back in JavaScript:
const getPreview = post => {
let test = preview => preview.size && preview.size < 200 * 1024 && qf( preview ) > 20,
thumbnailer = source => p => buildThumbnail( source( p ) );
return Until( test, post )
.surely( getFeaturedImage )
.surely( thumbnailer( getFeaturedImage ) )
.surely( getFirstImage )
.surely( thumbnailer( getFirstImage ) )
.surely( getDefaultThumbnail() )
.bind( this.convertToBase64 )
.value;
);
Wow – that was a bit heftier. That’s a function that attempts to build a suitable image preview for a blog post and it tries in sequence the featured image, then a scaled down version of the featured image, then the first image in the post, then a thumbnail of the first image in the post, then a default thumbnail image, finally converting that value to base64 if and only if we found a qualifying preview image. Whew! Note that we store the post object in the monad and it stays there until we get a successful value. That means we don’t have to pass it in at each stage, because it will be applied to those functions that get passed into surely()
, equivalent to surely( getFirstImage( post ) )
.
Remember that these are lazy evaluations and the chain stops on the first valid update, so it’s a pretty efficient way of getting to your final value.
Honestly I don’t know if someone else has already done something like this. It definitely fills a need I have when writing functions that don’t do much more than build a data structure with varying levels of fallback values. This is also made possible by Anthony Ferrara’s Monad-PHP library – a very big thanks to him!