Bringing Monads to PHP

Original author: Anthony Ferrara
  • Transfer
  • Tutorial
http://hermetic.com/jones/in-operibus-sigillo-dei-aemeth/the-circumference-and-the-hieroglyphic-monad.html


Most recently, I played with some functional languages ​​and their concept, and noticed that some ideas of functional programming can be applied to the object code that I wrote earlier. One of the ideas worth talking about is the Monads. This is something that every coder is trying to write a tutorial about in a functional language, as this is a cool, but difficult to understand thing. This post will not be a tutorial on Monads ( there ’s this wonderful translation from AveNat for this ) - rather a post on how to use them to good use 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 container of state, where different Monads do different things about this state. But it’s better to read it . We will also assume that we have 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 Monad base class .

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 and we need only two of them - the constructor and bind. Although the other two significantly simplify our lives.

The constructor creates a new Monad (your cap) - takes the value and saves it in the protected property, extract does the opposite. This is not a standard Monad function, but I added it because PHP is not a very 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 an input callable value and calls it using the value that is in the Monad. That is, this function does not even know what works with the Monad, and this is exactly where the whole power of the idea manifests itself.

use MonadPHP\Identity;
$monad = Identity::unit(10);
$newMonad = $monad->bind(function($value) {
    var_dump($value);
    return $value / 2;
}); // выводит int(10)
$b = $newMonad->extract();
var_dump($b); // выводит int(5)

Everything is simple! And to no avail.

What is the point?


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

Maybe Monad can be used to abstract from null ( here usually comes the understanding that it’s worth reading the same post, which I’ll do now .. ). In this case, bind will only call callback when the stored Monad value is not null. This will save your business logic from the nested conditions, so try to refactor this code:

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

Cool, but what happens if item does not have a parent ( getParent () will return null )? There will be a call error to a null object (call to a member function on a non-object). This problem can be solved somehow like this:

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

And it’s possible like this, with Monads:

function getGrandParentName($item) {
  $monad = new Maybe($item);
  $getParent = function($item) {
    // может быть null, но нам уже без разницы!
    return $item->getParent();
  };
  $getName = function($item) {
    return $item->getName();
  }
  return $monad
            ->bind($getParent)
            ->bind($getParent)
            ->bind($getName)
            ->extract();
}

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

Another example


Suppose we want to get to call GrandParentName on an array of values ​​(get the name of the parent from the list of values). Alternatively, you can iterate it and call the method every time. But this can also be avoided.
Using ListMonad we can substitute an array of values ​​as one. Change our last method so that it accepts the 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);
}

Everything is 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(); 
//или
$monad = new ListMonad(array($item1, $item2, $item3)); 
// Сделаем какждый элемент массива инстансом Maybe Monad
$maybeList = $monad->bind(Maybe::unit);
$names = getGrandParentName($maybeList);
// array('name1', 'name2', null)

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

main idea


It lies in the fact that thanks to 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 just 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 redefine our method to start working with an array of objects.

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

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

Also popular now: