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