And again about monads in PHP

After reading this material on a languid and cool Friday evening, I still have a certain feeling of dissatisfaction and burning somewhere below. I sat with the zeal of a madman, updating comments in the hope that there would be a person who would say why this was happening and I would understand that I was not alone. But ... alas, this did not happen. After which I visited this creation and felt the same feeling and realized that something needs to be changed.
Debriefing
I will pour all my indignation on a specific monad, namely on optional values.
But first, take a look at the Monad root class code:
public static function unit($value) {
if ($value instanceof static) {
return $value;
}
return new static($value);
}
And immediately we are terrified - you can not put the monad in the monad!
We quickly eliminate this injustice in all functions:
//Вобщем просто убираем эти проверки
public static function unit($value) {
return new static($value);
}
Somehow the code immediately became more beautiful (because why do we need if_y O_o).
But since we can now have monads in monads, we need to take care of the functions for these situations. Actually:
//Типо аппликативный функтор
abstract public function fbind($function, array $args = array());
//Будет вытаскивать монаду из монады
abstract public function flatten();
So now let's move on to the Maybe monad.
What kind of infringement of the rights of NULL 'I, is the same value. And why the author did not fill it with additional specific functionality of the mind I can’t attach:
abstract class Maybe extends Monad {
abstract public function extractOrElse($val, array $args = array());
}
As you might guess, this method returns an internal or passed value to us.
But why $ args ? You ask. Yes, in general, why not pass a function that needs to be calculated only if we need this value. (Damn lack of call-by-name!)
So, now we will describe the classes Just and Nothing :
class Just extends Maybe {
public function extractOrElse($val, array $args = array()) {
return $this->value;
}
public function fbind($function, array $args = array()) {
$res = $this->runCallback($function, $this->value, $args)
if(res instanceof Maybe)
return $res;
else
throw new \InvalidArgumentException('Returned value must be an instanceof Maybe monad');
}
public function flatten() {
if($this->value instanceof Maybe)
return $this->value;
else
throw new Exception('Value of just is not an instance of Maybe monad');
}
}
class Nothing extends Maybe {
protected static $_instance = NULL;
final private function __construct() { }
final private function __clone() { }
final public static function getInstance(){
if(null !== static::$_instance){
return static::$_instance;
}
static::$_instance = new static();
return static::$_instance;
}
public function extractOrElse($val, array $args = array()) {
if (is_callable($val) || $val instanceof Closure)
return call_user_func_array($val, $args);
else
return $val
}
public function bind($function, array $args = array()) {
return $this;
}
public function fbind($function, array $args = array()) {
return $this;
}
public function flatten() {
throw new Exception("Nothing flatten call");
}
}
As the reader may have noticed, this is Singleton . Well, really, why do we need a lot of “nothing”?
Example
Let us examine a similar example as in the previous article .
We need to get the name of the parent of the parent .
Here is an example from that article:
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();
}
I think that the monad should already go to the input of the function. Maybe it’s possible that there may not be a value. And of course, the element may not have a parent, which, as it were, hints at the getParent value type . And then it turns out:
function getGrandParentName($item) {
$getParent = function($item) {
return $item->getParent();
};
$getName = function($item) {
return $item->getName();
}
return $item
->fbind($getParent)
->fbind($getParent)
->bind($getName)
->extractOrElse("default");
}
And now, if on the way to getting we meet Nothing , then default will return , otherwise the name.
Well, my soul is a little calmer regarding monads in php. Well, or at least regarding Maybe ...
I want to hear opinions about this and the previous article. And also about the “library” , what would you add there, because it was just my opinion, and we have a lot of goals.
PS Do not really throw stones, all the same, first one paper.
UPD thanks for a cleaner image of TheRaven :)