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



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

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

Все свое негодование я вылью на конкретную монаду, а именно на опциональные значения.
Но для начала заглянем в код корневого класса 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 :)
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 8

    +1
    А почему бы Nothing не объявить просто как константу типа Maybe, вместо синглетона?
      0
      Паттерны, все дела. Это уже дело вкуса, никто не мешает и создавать каждый раз, когда надо, экземпляры.
      +2
      Вопрос: зачем вообще библиотека монад в PHP? Это же неидеоматичное использование языка.

      В Haskell монады крайне полезны и идеоматичны, например, они обеспечивают статические гарантии на уровне системы типов: разделение чистого кода и кода с побочными эффектами, выделение STM-транзакций и т.п. Там даже сахар на уровне языка для них есть.

      Цель — популяризация функционального подхода? Честно говоря, я и в компактной нотации Haskell с трудом осваиваю нетривиальные концепции теории категорий, а когда они приправлены ООП и всяческой шелухой, то я и вовсе теряюсь.
        +2
        Да конечно особо они не нужны (да что уж там, я бы назвал это баловством), особенно в нетепизированных то языках. Но если пользоваться с умом, то код станет в разы чище и читабельнее.
        И да конечно же, да прибудет функциональщина, за лямбду!
        P.S. Тут скорее идет не приправление функциональщины ООП, а наоборот. Сколько раз я проклинал NPE после использования скалы с Option'ами…
          0
          Многим кажется, что они поняли монады после знакомства Maybe. На самом деле всё немножно сложнее, ведь каждую монаду приходится осознавать заново. Есть ведь ещё Reader, State, Cont etc., трансформеры монад и проблемы коммутации, связь с аппликативными функторами и комонадами, много всего.

          > код станет в разы чище и читабельнее.
          Код повысит входную планку для читателей. Для просвещённых он будет читабельнее, для большинства же — врядли.

          > Сколько раз я проклинал NPE после использования скалы с Option'ами
          Maybe/Optional не спасают от ошибок, они лишь указывают о возможности ошибки на уровне системы типов. Без статической проверки типа от них гораздо меньше прока.
            +1
            Многим кажется, что они поняли монады после знакомства Maybe. На самом деле всё немножно сложнее, ведь каждую монаду приходится осознавать заново.

            естественно каждая монада личность :)
            Код повысит входную планку для читателей. Для просвещённых он будет читабельнее, для большинства же — врядли.

            Согласен, но начать читать не так уж и сложно.
            Maybe/Optional не спасают от ошибок, они лишь указывают о возможности ошибки на уровне системы типов. Без статической проверки типа от них гораздо меньше прока

            Прока может быть и меньше, но все же если придерживаться того, что null — это значение, а не его отсутствие, и пользовать где нужно Maybe. Должны полностью убрать возможность пропустить какой нибудь null там где это не ожидается
        0
        del
          0
          Спасибо за дополнение, тем более что сам я в функциональщине полный ноль.
          По поводу неполноценности монад в той статье могу лишь сказать, что изначальная статья писалась как раз с тем расчетом, чтобы показать, что некоторые идеи из ФП довольно удобны, и очень вероятно, что у Феррары просто напросто не было задачи написать правильные монады с точки зрения ФП.

          Only users with full accounts can post comments. Log in, please.