Функции высших порядков и монады для PHP`шников

    Среди PHP программ преобладает процедурный или в последних версиях частично объектно-ориентированный стиль программирования. Но можно писать и иначе, в связи с чем хочется рассказать о функциональном стиле, благо кое-какие инструменты для этого имеются и в PHP.


    Поэтому мы рассмотрим реализацию парсера JSON в виде простейших функций и функций их комбинирующих в более сложные, постепенно дойдя до полноценного парсера JSON формата. Вот пример кода, который мы получим:


    $jNumber = _do(function() {
        $number  = yield literal('-')->orElse( literal('+') )->orElse( just('') );
        $number .= yield takeOf('[0-9]')->onlyIf( notEmpty() );
        if ( yield literal('.')->orElse( just(false) ) ) {
            $number .= '.'. yield takeOf('[0-9]');
        }
        return +$number;
    });

    Кроме собственно функционального подхода можно обратить внимание на использование классов для создания DSL-подобного синтаксиса и на использование генераторов для упрощения синтаксиса комбинаторов.


    UPDATE само-собой парсинг JSON уже давно решенная задача и конечно готовая и протестированная функция на C будет работать лучше. Статья использует эту задачу как пример для объяснения функционального подхода. Так же не пропагандируется использование именно такого кода в продакшене, каждый может почерпнуть себе какие-то идеи, которые могут упростить код и жизнь.


    Полный код находится на github.


    Функциональный стиль


    Как программист справляется с огромной сложностью программ? Он берет простые блоки и строит из них более сложные, из которых строятся еще более сложные блоки и в конце-концов программа. По крайней мере так было после появляния первых языков с подпрограммами.


    В основе процедурного стиля лежит описание процедур, которые вызывают другие процедуры вместе меняющие какие-то общие данные. Объекто-ориентированный стиль добавляет возможность описывать структуры данных, составленные из других структур данных. Функциональный же стиль использует композицию (соединение) функций.


    Чем же отличается композиция функций от композиции процедур и объектов? Основа функционального подхода — чистота функций, это значит, что результат работы функций зависит только от входных параметров. Если функции чисты, то гораздо проще предсказать результат их композиции и даже создать готовые функции для преобразования других функций.


    Именно функции принимающие и/или возвращающие в качестве результата другие функции называются функциями высшего порядка и представляют тему этой статьи.


    Какую задачу будем решать?


    Для примера возьмем задачу, которую не очень просто решить целиком и посмотрим как функциональный подход поможет нам эту задачу упростить.


    Для примера попробуем сделать парсер формата JSON, который из строки JSON получит соответствующий PHP объект: число, строку, список или ассоциированный список (со всеми возможными вложенными значениями конечно).


    Что такое парсер?


    Начнем с самых простых элементов: мы пишем парсер, что же это такое? Парсер это функция, которая берет строку и в случае успеха возвращает пару значений: результат разбора и остаток строки (если разбираемое значение занимало не всю строку) или пустой набор, если разобрать строку не удалось:


    Parser: string => [x,string] | []

    Например если у нас есть функция-парсер number то мы могли бы написать такие тесты:


    assert(number('123;') === [123,';']);
    assert(number('none') === []);

    DISCLAMER: PHP имеет не слишком удобный синтаксис для работы с функциями, поэтому для более простого и понятного кода мы будем использовать класс, который является ни чем иным, как просто оберткой вокруг функции парсера и нужен для указания типов и для использования удобного синтаксиса цепочных вызовов, о чем подробнее поговорим дальше.


    class Parser {
        const FAILED = [];
        private $parse; 
        function __construct(callable $parse) {
            $this->parse = $parse;
        }
        function __invoke(string $s): array {
            return ($this->parse)($s);
        }
    }

    Но будем помнить, что Parser это не более чем функция string => array.


    Для удобства так же введем функцию parser, которую будем использовать вместо вызова конструктора new Parser для краткости:


    function parser($f, $scope = null) { 
        return new Parser($f->bindTo($scope)); 
    }

    Простейшие парсеры


    Итак, мы разобрались что такое парсеры, но ни одного так и не написали, давайте это исправим. Вот пример парсера, который всегда возвращает 1, независимо от исходной строки:


    $alwaysOne = parser(function($s) {
        return [1, $s];
    });
    assert($alwaysOne('123') === [1, '123']);

    Полезность этой функции не очевидна, давайте сделаем ее более общей и объявим функцию, которая позволит создавать подобный парсер для любого значения:


    function just($x): Parser {
        return parser(function($s) use ($x) {
            return [ $x, $s ];
        });
    }

    Пока все просто, но все еще не очень полезно, ведь мы хотим парсить строку, а не возвращать всегда одно и то же. Давайте сделаем парсер, который возвращает первые несколько символов входной строки:


    function take(int $n): Parser {
        return parser(function($s) use ($n) {
            return strlen($s) < $n ? Parser::FAILED : [ substr($s, 0, $n), substr($s, $n) ];
        });
    }
    
    test(take(2), 'abc', ['ab','c']);
    test(take(4), 'abc', Parser::FAILED);

    Уже лучше, первый наш парсер, который действительно разбирает строку! Напомню: мы описываем простейшие кирпичики из который сможем собрать более сложный парсер. Поэтому для полноты картины нам нехватает только парсера, который ничего не парсит вообще.


    function none(): Parser {
        return parser(function($s) {
            return Parser::FAILED;
        });
    }

    Он нам еще пригодится.


    Вот и все парсеры, которые нам нужны. Этого достаточно, чтобы разобрать JSON. Не верите? Осталось придумать способ собирать эти кирпичики в более сложные блоки.


    Собираем кирпичики вместе


    Поскольку мы решили заняться функциональным программированием, то для комбинирования функций парсеров в более сложные парсеры логично использовать функции!


    Например если у нас есть парсеры first и second и мы хотим применить к строке любой из них мы можем определить комбинатор парсеров — функцию, создающую новый парсер на основе существующих:


    function oneOf(Parser $first, Parser $second): Parser {
        return parser(function($s) use ($first,$second) {
            $result = $first($s);
            if ($result === Parser::FAILED) {
                $result = $second($s);
            }
            return $result;
        });
    }
    test(oneOf(none(),just(1)), '123', [1,'123']);

    Но, как уже упоминалось выше, такой синтаксис может быстро стать нечитаемым (например oneOf($a,oneOf($b,oneOf($c,$d)))), поэтому мы перепишем эту (и все следующие) функции как методы класса Parser:


    function orElse(Parser $alternative): Parser {
        return parser(function($s) use ($alternative) {
            $result = $this($s);
            if ($result === Parser::FAILED) {
                $result = $alternative($s);
            }
            return $result;
        }, $this); // <- вот зачем в функции parser был bindTo: чтобы использовать $this в функции
    }
    test(none()->orElse(just(1)), '123', [1,'123']);

    Так уже лучше, можно писать $a->orElse($b)->orElse($c)->orElse($d) вместо того, что было выше.


    И еще одна, не такая простая, но зато гораздо более мощная функция:


    function flatMap(callable $f): Parser {
        return parser(function($s) use ($f) {
            $result = $this($s);
            if ($result != Parser::FAILED) {
                list ($x, $rest) = $result;
                $next = $f($x);
                $result = $next($rest);
            }
            return $result;
        }, $this);
    }

    Давайте разберемся с ней подробнее. Она принимает функцию f: x => Parser, которая принимает результат парсинга нашего существующего парсера и возвращает на его основе новый парсер, который продолжает разбор строки с того места, где остановился наш предыдущий парсер.


    Например:


    test(take(1), '1234', ['1','234']);
    test(take(2), '234',  ['23', '4']);
    test(
        take(1)->flatMap(function($x) { # x -- результат парсинга take(1)
            return take(2)->flatMap(function($y) use ($x) { # y -- результат парсинга take(2) 
                return just("$x~$y"); # -- финальный результат
            });
        }),
        '1234',
        ['1~23','4']
    );

    Таким образом мы скомбинировали take(1), take(2) и just("$x~$y") и получили довольно сложный парсер, который сначала парсит один символ, за ним еще два и возвращает их в виде $x~$y.


    Основная особенность проделанной работы состоит в том, что мы описываем, что делать с результатами парсинга, но сама разбираемая строка тут не участвует, у нас нет возможности ошибиться и тем, какую часть строки куда передавать. А дальше мы увидим как сделать синтаксис таких комбинаций более простым и читабельным.


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


    function onlyIf(callable $predicate): Parser {
        return $this->flatMap(function($x) use ($predicate) {
            return $predicate($x) ? just($x) : none();
        });
    }

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


    function literal(string $value): Parser {
        return take(strlen($value))->onlyIf(function($actual) use ($value) {
            return $actual === $value;
        });
    }
    test(literal('test'), 'test1', ['test','1']);
    test(literal('test'), 'some1', []);

    DO нотация


    Мы уже описали простейшие парсеры take, just и none, способы их комбинации (orElse,flatMap,onlyIf) и даже описали с их помощью парсер литералов literal.


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


    В связи с этим подсмотрим как решают эту проблему другие языки. Так в языках Haskell и Scala есть весьма удобный синтаксис для работы с подобными вещами (они даже имеют свое название — монады), называется он (в Haskell) DO-нотация.


    Что по-сути делает flatMap? Он позволяет описать что делать с результатом парсинга не совершая собственно парсинга. Т.е. процедура как-бы приостанавливается до момента получения промежуточного результата. Для реализации подобного эффекта можно использовать новый для PHP синтаксис — генераторы.


    Генераторы


    Давайте сделаем небольшое отступление и рассмотрим что такое генераторы. В PHP 5.5.0 и выше появилась возможность описать функцию:


    function generator() {
        yield 1;
        yield 2;
        yield 3;
    }
    foreach (generator() as $i) print $i; # -> 123

    Что более интересно для нас, данные можно не только получать из генератора, но и передавать в него через yield, и даже с 7 версии получить результат генератора через getReturn:


    function suspendable() {
        $first = yield "first";
        $second = yield "second";
        return $first.$second;
    }
    $gen = suspendable();
    while ($gen->valid()) {
        $current = $gen->current();
        print $current.',';
        $gen->send($current.'!');
    }
    print $gen->getReturn();
    # -> first,second,first!second!

    Это можно использовать, чтобы спрятать вызовы flatMap от программиста.


    flatMap используя комбинаторы


    function _do(Closure $gen, $scope = null) {
        $step = function ($body) use (&$step) {
            if (! $body->valid()) {
                $result = $body->getReturn();
                return is_null($result) ? none() : just($result);
            } else {
                return $body->current()->flatMap(
                    function($x) use (&$step, $body) {
                        $body->send($x);
                        return $step($body);
                    });
            }
        };
        $gen = $gen->bindTo($scope);
        return parser(function($text) use ($step,$gen) {
            return $step($gen())($text);
        });
    }

    Эта функция берет каждый yield в генераторе (который содержит парсер, результат которого мы хотим получить) и комбинирует его с оставшимся фрагментом кода (в виде рекурсивной функции step) через flatMap.


    То же самое без рекурсии и функции flatMap можно было бы записать так:


    function _do(Closure $gen, $scope = null) {
        $gen = $gen->bindTo($scope); # ради использования $this в функции
        return parser(function($text) use ($gen) {
            $body = gen();
            while ($body->valid()) {
                $next = $body->current();
                $result = $next($text);
                if ($result === Parser::FAILED) {
                    return Parser::FAILED;
                }
                list($x,$text) = $result;
                $body->send($x);
            }
            return $body->getReturn();
        });
    }

    Но первая запись более интересна тем, что она не привязана особо к парсерам, от них там только функции flatMap, just и none (да и то, можно было бы переписать just чтобы обрабатывать null особым образом и обойтись без none).


    Объекты, которые можно комбинировать с помощью двух методов flatMap и just называют монады (это немного упрощенное определение) и этот же код можно использовать, чтобы писать комбинаторы для промисов (Promise), опциональных значений (Maybe,Option) и многих других.


    Но ради чего же мы писали эту не самую простую функцию? Для того, чтобы дальнейшее использование flatMap было гораздо легче. Сравним один и тот же код с чистым flatMap:


    test(
        take(1)->flatMap(function($x) {
            return take(2)->flatMap(function($y) use ($x) {
                return just("$x~$y");
            });
        }),
        '1234',
        ['1~23','4']
    );

    и тот же самый код, но написанный через _do:


    test(
        _do(function() {
            $x = yield take(1);
            $y = yield take(2);
            return "$x~$y";
        }),
        '1234',
        ['1~23','4']
    );

    Результирующий парсер делает то же самое тем же способом, но читать и писать такой код гораздо проще!


    Строим более сложные парсеры и комбинаторы


    Теперь, используя эту нотацию мы можем написать еще несколько полезных парсеров:


    function takeWhile(callable $predicate): Parser {
        return _do(function() use ($predicate) {
            $c = yield take(1)->onlyIf($predicate)->orElse(just(''));
            if ($c !== '') {
                $rest = yield takeWhile($predicate);
                return $c.$rest;
            } else {
                return '';
            }
        });
    }
    function takeOf(string $pattern): Parser {
        return takeWhile(function($c) use ($pattern) {
            return preg_match("/^$pattern$/", $c);
        });
    }
    test(takeOf('[0-9]'), '123abc', ['123','abc'   ]);
    test(takeOf('[a-z]'), '123abc', [   '','123abc']);

    И полезные методы класса Parser для повторяющихся элементов:


    function repeated(): Parser {
        $atLeastOne = _do(function() {
            $first = yield $this;
            $rest = yield $this->repeated();
            return array_merge([$first],$rest);
        },$this);
        return $atLeastOne->orElse(just([]));
    }
    function separatedBy(Parser $separator): Parser {
        $self = $this;
        $atLeastOne = _do(function() use ($separator) {
            $first = yield $this;
            $rest = yield $this->prefixedWith($separator)->repeated();
            return array_merge([$first], $rest);
        },$this);
        return $atLeastOne->orElse(just([]));
    }

    JSON


    Каждый из написанных нами парсеров и комбинаторов в отдельности просты (ну может кроме flatMap и _do, но их всего два и они очень универсальны), но используя их теперь нам не составит труда написать парсер JSON.


    jNumber = ('-'|'+'|'') [0-9]+ (.[0-9]+)?


    $jNumber = _do(function() {
        $number  = yield literal('-')->orElse(literal('+'))->orElse(just(''));
        $number .= yield takeOf('[0-9]');
        if (yield literal('.')->orElse(just(false))) {
            $number .= '.'. yield takeOf('[0-9]');
        }
        if ($number !== '')
            return +$number;
    });

    Код вполне самодокументирующийся, читать и искать в нем ошибки довольно просто.


    jBool = true | false


    $jBool = literal('true')->orElse(literal('false'))->flatMap(function($value) {
        return just($value === 'true');
    });

    jString = '"' [^"]* '"'


    $jString = _do(function() {
        yield literal('"');
        $value = yield takeOf('[^"]');
        yield literal('"');
        return $value;
    });

    jList = '[' (jValue (, jValue)*)? ']'


    $jList = _do(function() use (&$jValue) {
        yield literal('[');
        $items = yield $jValue->separatedBy(literal(','));
        yield literal(']');
        return $items;
    });

    jObject = '{' (pair (, pair)*)? '}'


    $jObject = _do(function() use (&$jValue) {
        yield literal('{');
    
        $result = [];
        $pair = _do(function() use (&$jValue,&$result) {
            $key = yield takeOf('\\w');
            yield literal(':');
            $value = yield $jValue;
            $result[$key] = $value;
            return true;
        });
        yield $pair->separatedBy(literal(','));
        yield literal('}');
    
        return $result;
    });

    jValue = jNull | jBool | jNumber | jString | jList | jObject


    $jValue = $jNull->orElse($jBool)->orElse($jNumber)->orElse($jString)->orElse($jList)->orElse($jObject);

    Вот и готов парсер JSON jValue! Причем выглядит не так уж непонятно, как казалось вначале. Есть некоторые замечания к производительности, но они решаются заменой способа разделения строки (например вместо string => [x, string] можно использовать [string,index] => [x,string,index] и избежать многократного разбиения строки). Причем для изменения такого рода достаточно переписать just, take и flatMap, весь остальной код, построенный на их основе останется без изменений!


    Заключение


    Моей целью было конечно не написание очередного парсера JSON, а демонстрация того, как написание маленьких простых (и функционально чистых) функций, а так же простых способов их комбинирования позволяет строить сложные функции простым способом.


    А в простом и понятном коде и ошибок меньше. Не бойтесь функционального подхода.

    Поделиться публикацией

    Похожие публикации

    Комментарии 45
      +8
      Всё хорошо, но ваш код лично для меня сложно читать…
        +2
        Соглашусь, первый вариант функции _do просто взрывает мозг
          0
          Да тут всё разносит мозг, к стати моманды это опечатка или есть такое слово?
            +2
            Момнады это у вас опечатка, а монады есть
          0
          В принципе «сломать» мозг это отчасти цель этой статьи, для повышения гибкости, так сказать.
          А какой код кроме _do функции например сложный?
            0
            к примеру этот код
            function take(int $n): Parser {
                return parser(function($s) use ($n) {
                    return strlen($s) < $n ? Parser::FAILED : [ substr($s, 0, $n), substr($s, $n) ];
                });
            }
            


            тяжело воспринимается 2-ва return и тернарный оператор не к месту к стати…
              0
              Ничего сложно, довольно симпатичный код.
              Выглядит довольно таки красиво.
                +2
                Кому как многие привыкают читать код линейно с верху в низ без надобности возврата, к предыдущем участкам и чтением строки что бы понять что именно возвращается.
                К примеру
                      if(strlen($s) < $n) {
                           return Parser::FAILED;
                      } else {
                           return array(
                                  substr($s,0,$n),
                                  substr($s,$n)
                           );
                      }
                


                Согласитесь выглядит более читабельней чем

                    return strlen($s) < $n ? Parser::FAILED : [ substr($s, 0, $n), substr($s, $n) ];
                


                Ну и чем плох тернарный оператор, к примеру, всё просто, у вас новая вводная вам надо и при strlen($s) == $n вернуть Parser::FAILED
                многие привыкшие к тенарным операторам сделают это так

                    return strlen($s) < $n ? Parser::FAILED : strlen($s) == $n ? Parser::FAILED : [ substr($s, 0, $n), substr($s, $n) ] ;
                


                Что приводит к тому что человек не сразу поймёт что возвращается, становиться не очевидным просто.
                  0
                  >Согласитесь выглядит более читабельней чем

                  Да вроде одинаково…

                  >многие привыкшие к тенарным операторам сделают это так

                  Уверен, что многие сделают примерно так: return strlen($s) <= $n? Parser::FAILED: [ substr($s, 0, $n), substr($s, $n) ];
          +1
          А в простом и понятном коде и ошибок меньше

          Я бы не сказал что приведенный в статье код «простой и понятный».
          Для меня он показался наоборот чем-то дико-странным, непонятным и нечитаемым.
            0
            пишу на скала, код в статье — сложна))), а говорят скала сложна.
            автор, отличная статья, ждем продолжения?
            +1

            Было бы неплохо увидеть финальный код.
            И сравнить с парсером, написанным с использованием како-то другого подхода.


            Лично мне код не показался очень простым и понятным. Но возможно, простота познается в сравнении, и на деле другие реализации были бы гораздо сложнее?


            Кстати, по-моему, написанный парсер строковых литералов не обработает строку с двойной кавычкой внутри ("I \"am\" inside") :)

              0

              Финальный код приведен в конце, правда ссылку на репозиторий я похоже забыл, вот он.
              Вот пример на js, правда на js и мой код выглядел бы на порядок проще, разница в тестируемости отдельных частей и декларативности.
              В парсинге строк я действительно немного схалтурил :)

              +1
              Всё, конечно круто, если не брать во внимание то, что вам уже и так сказали о читабельности и то, что 90% PHP-программистов пост-советского пространства и с ООП совладать не в силе… На стандартный вопрос отличий private и protected или абстрактного класса от интерфейса на собеседовании далеко не каждый отвечает, неговоря уже о таком рядом плывущем ките, как SOLID. В подавляющем большинстве случаев, кандидат даже не предпринимает попытки понять базовые паттерны проектирования и даже пресловутые синглтоны и абстрактные фабрики зазубриваются. А вы тут решили ФП вбросить, опасную игру затеяли. Но еще раз повторюсь, другим 10% полезно иногда смотреть на другие парадигмы, как минимум для общего развития. Лично я проникся ФП на фронтенде, но на бекенд пока руки не доходят, да и бизнес мне за такие шутки по рукам надает :D
                0
                Вы так говорите, как будто функциональщина лучше и сложнее ООП подхода. Посмотрите на пример, ад из колбеков замаскированный через бинд, сложный в поддержке и трудночитаемый. У функциональщины свои плюшки, но ради них жертвуют человекопонятным кодом.

                Ну, а по поводу знаний ООП, мусора везде хватает, на то есть эйчары и собеседования, чтобы его фильтровать, в нормальные компании такие не попадают.
                  0
                  Нет, я говорю ни в коем случае не о сложности. ООП мейнстримней, материалы по нему намного доступней читателям, незнакомым с английским языком(хотя по моему убеждению ЛЮБОЙ разработчик должен владеть английским хотя бы на уровне чтения документации, но практика поиска сотрудника в мультиязыковую команду показала, что это далеко не так).
                    0
                    А что вы хотите от страны где 80% не имеют загранпаспорта и дальше крыма не выезжали, а дома пилят проекты на битриксе. Но это вымирающее меньшинство. В Беларуси или Украине таких проблем нет, практически везде чтение документации на английском и ооп — обязательные условия, т.к. проекты западные.

                    Я так понимаю, вы искали среди так называемых фрилансеров, но это отдельная группа, где за редким исключение ни то что программиста, но даже человека адекватного найти тяжело. У нас же сейчас обратная ситуация, слишком много начитанных специалистов, которые любят паттерны, ddd и всякие абстракции больше, чем результат.
                      0
                      Я искал любых людей, готовых к релокации в Бангкок на довольно хороших условиях, с оплатой перелетов\проживания и довольно конкурентной з\п, тем более для Бангкока. Кстати, про фрилансеров не совсем согласен. В русском фрилансе еще может быть(честно — не знаю), но на том же upWork, программисты с рейтами выше 15-20$\час как правило адекватные, конечно, с исключениями. Я сам фрилансер по большому счету, только еще иногда езжу к клиенту «на место» при необходимости, так что фрилансеров буду с пеной у рта отстаивать :D

                      Но это вымирающее меньшинство

                      Увы, наша страна делаеи шаги отнюдь не в сторону глобализации, а сказки про злого Обамку не мотивируют учить бездуховный английский.

                      У нас же сейчас обратная ситуация, слишком много начитанных специалистов, которые любят паттерны, ddd и всякие абстракции больше, чем результат

                      Ну вот я находясь в Москве очно собеседовал людей и с паттернами\начитанностью были проблемы. Обратную сторону тоже понимаю, но, по-моему, любая книжка по паттернам пытается предупредить о том, что не стоит заболевать «паттерном головного мозга». Ну и вообще думаю, что это тоже лечится, при том проще, чем незнание. Кстати, ФП в этом плане далеко не последний инструмент. Мне когда-то помогло в определенной степени.
                    0

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

                      0
                      В ООП тоже всё легко тестировать если пользуешься DI и пользуешься фаулеровскими заветами вроде разделяй запрос и модификатор.
                      Вы в статье очень удачно использовали объекты, тем самым продемонстрировав, что ООП не отменяет функциональных подходов, а очень удачно дополняется ими.
                        +1

                        На самом деле в этом примере ООП не используется, а класс используется только ради инфиксной нотации ($this->orElse($that) вместо orElse($this, $that)).
                        А у ООП достаточно недостатков, начиная от хрупкости базового класса. Это видно даже в количестве паттернов, большей части из которых в функциональной парадигме просто нет, за ненадобностью.

                    0
                    90% PHP-программистов пост-советского пространства и с ООП совладать не в силе…

                    В 90% ваш ООП не нужен.
                    Взяли за идею, мол ООП корова, которая спасет мир. Но это не так.
                      0
                      +100500.

                      Нужно задачу решать самым простым способом, а не городить огород.
                        0
                        Самым простым способ и думать о будущем, да и задачи бывают разные 'Hello world' и 'интернет магазин' это разные по объёму и методики решения задачи.
                          0
                          Самым простым, необходимым для решения задачи… :)
                        0
                        Может вы не будете за меня решать, что я решил, а что нет? А еще не будете решать за сообщество, которое почему-то внезапно в большинстве своем использует ООП — наверное не просто так, м? Ваши высказывания говорят о том, что и в ФП я знаю побольше вашего. Для всего есть свои области применения. ООП очень удобно там, где нужно общаться на одном языке с бизнесом, например. Для этого у нас есть DDD. А что для этого есть в ФП?
                          0

                          Не сказал бы, что DDD может быть использовано исключительно с ООП, наоборот, мне кажется описание действий, которые можно производить с данными более гибко, чем попытка определить какому объекту действие принадлежит.

                            0
                            У вас ошибка уже в том, что вы пытаетесь производить действия исключительно с данными. Мой менеджер ничего не знает и не должен знать о данных, с которыми я провожу эти действия. Ему гораздо понятней объекты реального мира: нефтяная труба, ящик помидоров или клиент банка, нежели (радиус, материал, длина конструкции, проводимость), (длина, ширина, вместимость), (номер паспорта, баланс,… x100 других параметров). Это если не брать во внимание, что в бизнесе не бывает чистых функций и выдача кредита физ. лицу принципиально отличается от выдачи кредита юр.лицу.
                              0
                              Обычно менеджеры оперируют не только объектами, но и процессами, а с этим у ООП значительно хуже, чем у ФП. А вообще ООП и ФП отлично дополняют друг друга именно в сфере моделирования бизнеса процессов. Да и многие ООП паттерны являются по сути переводом на ООП-нотацию сущностей, которые в ФП есть, как говорится, из коробки.
                                0

                                Нефтяная труба и ящик помидоров это тоже данные, функциональный подход прекрасно справляется с такими сущностями, зато не возникает вопросов "сьесть" это метод человека или помидора? Или еще хуже, чей метод "создать продукт питания"?
                                А что там внутри длинна/ширина это нас не должно волновать, абстракция там тоже есть.

                                  0
                                  Это всё справдливо только если только мы говорим не о чистых ФП. А говоря ФП, во всяком случае я подразумеваю ЧИСТОЕ ФП. И самый высокий уровень абстракции, который мы там можем себе позволить — Функции высших порядков, что все еще очень далеко от ООП по уровню абстракции. Если не прав, буду рад увидеть пример. Я даже нагуглить не смог.
                                    +1

                                    Возьмем для примера чистый функциональный язык Haskell, в нем делается так:


                                    module Fruits (Tomato, grow, eat) where
                                    
                                    data Tomato = Tomato String Int
                                    
                                    grow weight = Tomato "Good one" weight
                                    eat (Tomato name _) = name ++ " was good"

                                    Извне модуля видно только имя типа Tomato и функции, внутренняя структура абстрагирована.
                                    Полиморфизм тут тоже есть, если интересно, называется Type Classes.

                              0
                              А еще не будете решать за сообщество, которое почему-то внезапно в большинстве своем использует ООП — наверное не просто так, м?

                              Мне сложно общаться с людьми, которые меняют свое мнение на лету.
                              Ты уж определись, либо 90% не могут совладать с ООП, либо сообщество только ООП использует. А то, я тебя не понимаю.
                              И не надо ванговать, что и кто лучше знает, я тут своими знаниями не делился.
                                0
                                Ты уж определись, либо 90% не могут совладать с ООП, либо сообщество только ООП использует

                                Если у тебя не достаточно логических способностей понять, что сообщество программистов не ограничивается только российскими программистами и то, что люди часто используют то, чего не понимают(ООП — как пример) это твои проблемы. Это если не учитывать, что я ни разу не сказал, что сообщество использует ТОЛЬКО ООП. Что-то я не вижу от тебя ни одной публикации по ФП, так что собирай свой жир и иди троллить куда-нибудь в другое место.
                                  0
                                  Ну и сумбур.
                                    –1
                                    Отвечу всё таки.
                                    Ты делаешь утверждение, что 90% Похапэшников в СНГ, не могут совладать с ООП.

                                    1. Какое сообщество программистов? Ты четко указал, что мы говорим о СНГ. И что в СНГ 90% похапэшников, не могут в ООП.
                                    2. Выходя из пункта 1, ты не следишь, за тем что пишешь.
                                    3. Довод, что я не пишу тут статьи, просто смешон.

                                    Прошу не пиши мне больше, я общаюсь с адекватными людьми.
                                    Спасибо.
                                      0
                                      Какое сообщество программистов? Ты четко указал, что мы говорим о СНГ. И что в СНГ 90% похапэшников, не могут в ООП.

                                      А, то есть ты решил взять 2 разных комментария, написанных в 2х разных контекстах, 2м разным людям, вырвать их из контекста и обвинить меня в том, что я мол за своими словами не слежу. Позволю себе такой же «опус».
                                      я общаюсь с адекватными людьми

                                      Но это не так

                                      Ну и иди общайся со своими неадекватами, клоун.
                            +1
                            Не приведи господь вам жить недалеко от меня если такой ваш код мне поддерживать придется.
                            Не надо натягивать чисто функциональный подход на PHP. Выглядит убого.
                              0

                              Познавательно, но не ясен практический профит. Использовать такое в проекте, учитывая что для большинства это действительно выглядит абракадаброй не резон. Просто по факту что так можно на php и в качестве головоломки...


                              $pair = _do(function() use (&$jValue,&$result) {
                                      $key = yield takeOf('\\w');
                                      yield literal(':');
                                      $value = yield $jValue;
                                      $result[$key] = $value;
                                      return true;
                                  });
                                  yield $pair->separatedBy(literal(','));

                              а как с автокомплитом метода separatedBy в IDE ?

                                0

                                _do декларирует результирующий тип Parser, так что все автокомплитится на ура. Я не утверждаю, что этот код хорош для продакшена, но отдельно взятые функции все же тестируются гораздо лучше, да и читаются довольно понятно, если привыкнуть.

                                  +1
                                  Вот именно,
                                  хороший код должен читаться без привычки
                                    0

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

                                      0
                                      Просто yield — очень новая фича.
                                      Ее почти никто не начал использовать. :)
                                  0
                                  Я бы добавил во вступлении, что инструменты ФП в PHP не просто есть, а есть с незапамятных времён и плох тот пехепешник, который не знает об array_(map|reduce|filter) и call_user_func

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                  Самое читаемое