
После прочтения вот этого материала томным и прохладным вечером пятницы у меня осталось некоторое чувство неудовлетворенности и жжения где то снизу. Я сидел со рвением безумца обновлял комментарии в надежде, что найдется человек который скажет отчего же это происходит и я пойму что я не одинок. Но… увы этого не произошло. После чего я посетил сие творение и почувствовал тоже чувство и понял, что что-то нужно менять.
Разбор полетов
Все свое негодование я вылью на конкретную монаду, а именно на опциональные значения.
Но для начала заглянем в код корневого класса 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 :)
