📜 ⬆️ ⬇️

We bring monads to PHP

http://hermetic.com/jones/in-operibus-sigillo-dei-aemeth/the-circumference-and-the-hieroglyphic-monad.html


Most recently, I was playing with some functional languages ​​and their concept, and noticed that some ideas of functional programming can also be applied to the object code that I wrote earlier. One of these ideas worth talking about is Monads. This is something that every coder is trying to write about in a functional language as this is a cool, but hard to understand thing. This post will not be a tutorial on Monads ( for this there is this wonderful translation from AveNat ) - rather a post on how to use them with benefit in PHP.

What are Monads?


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

Let's start with the simplest Monad - Identity Monad. There are only 4 functions in it, which 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 and we need only two of them - the constructor and bind. Although the other two greatly simplify our lives.

The constructor creates a new Monad (your cap) - it takes a value and saves it in the protected property, while extract does the opposite. This is not a standard Monad function, but I added it because PHP is not an entirely functional language.
The static unit function is a simple factory method. It 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 the callable value as input and calls it using the value in the Monad. That is, this function does not even know what 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; }); //  int(10) $b = $newMonad->extract(); var_dump($b); //  int(5) 

It's simple! And useless.

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 transformations with the Monad.

You can use Maybe Monad to abstract from null ( here you usually understand that you should read the very same post, which I will do now .. ). In this case, bind will call the callback only when the stored value of the Monad is not null. This will relieve your business logic from nested conditions, so we will try to refactor this code:

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

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

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

And you can, and so, with the 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 is a little more code here, but here's what has changed: instead of increasing the functionality procedurally step by step, we simply change the state. We start with the item, choose the parent, then select the parent again and then get the name. This 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 some kind of / danger.

Another example


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

It's simple. Now you can pass Maybe Monad and getGrandParentName will work as before. Only now you can pass a list of values ​​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 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 make a series of simple transformations. And having weighed the values ​​with different Monads, one can achieve the same logic as with ordinary noodle code, but at the same time duplicating nothing. Think about 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 does not simplify most of your code. But this really interesting idea has many uses in code that we write in OOP style. Therefore, play with Monads, create Monads and experiment with Monads!

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

Source: https://habr.com/ru/post/231801/


All Articles