Bringing Monads to PHP

Bringing Monads to PHP



More recently, I've been playing around with some functional languages ​​and their concepts, and I've noticed that some of the ideas of functional programming can be applied to the object code I wrote earlier. One such idea worth talking about is Monads. This is something that every coder tries to write a tutorial about in a functional language, as it's cool, but hard to understand. This post is not going to be a tutorial on Monads ( there is this wonderful translation from AveNat for that ) - rather a post on how to use them to your advantage in PHP.

What are Monads?


If the post above could not be read to the end (but in vain!), Then the Monad can be represented as a kind of container of the state, where different Monads do different things regarding this state. But it's better to read it . We will also assume that we have already played a little with the MonadPHP library from GitHub, since it will be used in the examples.


Let's start with the simplest Monad - Identity Monad. It has only 4 functions that are defined in the base class of the Monad .

namespace MonadPHP;
class Identity {
    public function __construct ($ value)
    public function bind ($ function)
    public function extract ()
    public static function unit ($ value)
}

There are only four methods here and we only need two of them - the constructor and bind. Although the other two greatly simplify our life.

The constructor creates a new Monad (your cap) - it takes the value and stores it in the protected property, while extract does the opposite. This is not really a standard Monad feature, but I added it because PHP is not a fully functional language.
The static unit function is a simple factory method. Looks to see if its input parameter is the current Monad and returns a new instance if not.
As a result, the most valuable method for us here is bind. It takes a callable value as input and calls it using whatever value is in the Monad. That is, this function does not even know that it works with the Monad and this is exactly where all the power of the idea manifests itself.

use MonadPHP/Identity;
$ monad = Identity :: unit (10);
$ newMonad = $ monad-> bind (function ($ value) {
    var_dump ($ value);
    return $ value/2;
}); //prints int (10)
$ b = $ newMonad-> extract ();
var_dump ($ b); //prints int (5)

It's that simple! And it's useless.

What's the point?


What is all the power? Ok, let's add some logic to bind (or other functions) to perform useful conversions with Monad.

You can use Maybe Monad to abstract from null ( this is where you usually understand that it's worth reading the same post, which I'll do now .. ). In such a case, bind will only call callback when the stored value of the Monad is not null. This will save your business logic from nested conditions, so let's try to refactor this code:

function getGrandParentName (Item $ item) {
    return $ item-> getParent () -> getParent () -> getName ();
}

Cool, but what if item has no parent ( getParent () returns null )? There will be an error call to a member function on a non-object. You can solve this problem somehow like this:

function getGrandParentName (Item $ item) {
if ($ item-> hasParent ()) {
    $ parent = $ item-> getParent ();
    if ($ parent-> hasParent ()) {
        return $ parent-> getParent () -> getName ();
    }
  }
}

And you can do it like this, with Monads:

function getGrandParentName ($ item) {
  $ monad = new Maybe ($ item);
  $ getParent = function ($ item) {
   //may be null, but we don't care!
    return $ item-> getParent ();
  };
  $ getName = function ($ item) {
    return $ item-> getName ();
  }
  return $ monad
            -> bind ($ getParent)
            -> bind ($ getParent)
            -> bind ($ getName)
            -> extract ();
}

Yes, there is a bit more code here, but here's what changed: instead of incrementing functionality procedurally step by step, we just change state. We start with item, select the parent, then select the parent again and then get the name. Such an implementation through Monads is closer to describing the very essence of our task (to get the name of the parent), while we avoided constant checks and thoughts about some kind of safety/danger.

Another example


Let's say we want to get call GrandParentName on an array of values ​​(get the name of the parent of a list of values). Alternatively, you can iterate over it and call the method every time. But even this can be avoided.
Using ListMonad we can substitute an array of values ​​as one. Let's change our last method to accept a Monad:

function getGrandParentName (Monad $ item) {
  $ getParent = function ($ item) {
    return $ item-> hasParent ()? $ item-> getParent (): null;
  };
  $ getName = function ($ item) {
    return $ item-> getName ();
  }
  return $ item
           -> bind ($ getParent)
           -> bind ($ getParent)
           -> bind ($ getName);
}

It's simple. Now you can pass Maybe Monad and getGrandParentName will work as before. Only now can a list of values ​​be passed and the method will continue to work as well. Let's try:

$ name = getGrandParentName (new Maybe ($ item)) -> extract (); 
//or
$ monad = new ListMonad (array ($ item1, $ item2, $ item3)); 
//Make each element of the array a Maybe Monad instance
$ maybeList = $ monad-> bind (Maybe :: unit);
$ names = getGrandParentName ($ maybeList);
//array ('name1', 'name2', null)

Once again, I will note that all the business logic remains the same! All changes came from outside.

main idea


It lies in the fact that thanks to the Monads, you can move away from unnecessary logic and focus on the logic of states. Instead of writing complex logic in a procedural style, you can simply do a series of simple transformations. And by weighting the values ​​with different Monads, you can achieve the same logic as from the usual noodle code, but without duplicating anything. Remember ListMonad - we didn't have to override our method to start working with an array of objects.

Of course, this is not a panacea, it will not simplify much of your code. But this really interesting idea has many uses in the code that we write in OOP style. So play with Monads, create Monads and experiment with Monads!

upd: Supplement to the current article from eld0727 -And again about monads in PHP