Pull to refresh

И снова про монады в PHP

Reading time3 min
Views14K


После прочтения вот этого материала томным и прохладным вечером пятницы у меня осталось некоторое чувство неудовлетворенности и жжения где то снизу. Я сидел со рвением безумца обновлял комментарии в надежде, что найдется человек который скажет отчего же это происходит и я пойму что я не одинок. Но… увы этого не произошло. После чего я посетил сие творение и почувствовал тоже чувство и понял, что что-то нужно менять.

Разбор полетов

Все свое негодование я вылью на конкретную монаду, а именно на опциональные значения.
Но для начала заглянем в код корневого класса Monad:

public static function unit($value) {
     if ($value instanceof static) {
         return $value;
     }
     return new static($value);
}


И сразу же повергаемся в ужас — нельзя засунуть монаду в монаду!

Быстро устраняем эту несправедливость во всех функциях:

//Вобщем просто убираем эти проверки
public static function unit($value) {
     return new static($value);
}


Как то и код сразу покрасивше стал (ибо зачем нам if'ы О_о).

Но так как у нас могут теперь быть монады в монадах надо позаботится о функциях для этих ситуаций. Собственно:

//Типо аппликативный функтор
abstract public function fbind($function, array $args = array());
//Будет вытаскивать монаду из монады
abstract public function flatten();


Итак теперь перейдем таки к монаде Maybe.

Что за ущемление прав NULL'я, это же тоже значение. И почему автор не наполнил ее дополнительным специфичным функционалом ума не приложу:

abstract class Maybe extends Monad {
    abstract public function extractOrElse($val, array $args = array());
}


Как несложно догадаться этот метод возвращает нам внутреннее или переданное значение.
Но зачем $args? Спросите вы. Да вобщем то, а почему бы и не передать функцию, которую нужно вычислить только в том случае, если это значение нам будет нужно. (Чертово отсутствие call-by-name!)

Итак, теперь опишем таки классы Just и 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");
    }

}


Как читатель мог заметить, это Singleton. Ну а действительно зачем нам много «ничего»?

Пример

Разберем аналогичный пример как и в предшествующей статье.
Нам нужно получить имя родителя родителя.

Вот пример из той статьи:

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();
}


Я же считаю что на вход функции должна уже идти монада Maybe коль уж возможно ситуация что значения может и не быть. Ну и конечно же у элемента может не быть родителя, что как бы намекает нам о типе значения getParent. И тогда выходит:

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");
}


И теперь, если на пути к получению у нас встретится Nothing, то вернется default, иначе имя.

Чтож, моя душа немного спокойнее относительно монад в php. Ну или по крайней мере, относительно Maybe

Хочу услышать мнения об этой и предыдущей статье. А также о «библитеке», что бы вы туда добавили, ведь это было только лишь мое мнения, а голов то у нас много.

P.S. Не особо кидайтесь камнями, все таки first one paper.
UPD благадарю за более «чистую» картинку TheRaven :)
Tags:
Hubs:
+18
Comments8

Articles