Объектно-ориентированное функциональное метапрограммирование

    Искусство каррирования
    Вдохновившись статьей Еще раз о каррировании и частичном применении в PHP, в голову пришла реализация частичного применения метода, именно метода, а не функции.

    Вводная часть


    Изначально определение каррирования дается как преобразование функции от пары аргументов в функцию, берущую свои аргументы по одному. Это преобразование было введено М. Шейнфинкелем и Г. Фреге и получило свое название в честь Х. Карри. Давайте теперь распространим это определение и на метод. Реализация этой идеи проста как 2 байта, но дает огромный потенциал. В этом и заключается доля метапрограммирования, когда методы можно создать в так сказать run-time, причем явно не описывая тело метода.

    Исходный код


    И так вот он класс с каррирующим методом, который и реализует (простите за каламбур) частичное использование метода класса. При этом, создается псевдометод, который и вызывается (опять скаламбурил) магическим методом __call():

    abstract class ACurry
    {
        /**
        * A curry method that returns a partial call of function
        * or a result of its execution, depending on the number
        * of parameters of the invoked method
        *
        * @param array $callback
        * @param array $args
        * @return callable
        */
    
        protected function curry($callback, $args = array())
        {
            return function() use($callback, $args)
            {
                $methodInfo = new ReflectionMethod(get_class($callback[0]), $callback[1]);
                if (count(array_merge($args, func_get_args())) >= $methodInfo->getNumberOfParameters()) {
                    return call_user_func_array($callback, $args);
                } else {
                    return $callback[0]->curry($callback, $args);
                }
            };
        }
    
        /**
        * Create a method $methodName by currying a method of $curryMethodName
        * with arguments $args
        *
        * @param string      $methodName
        * @param string      $curryMethodName
        * @param array       $args
        * @return ACurry
        */
    
        public function createMethod($methodName, $curryMethodName, $args = array())
        {
            $this->$methodName = $this->curry(array($this, $curryMethodName), $args);
            return $this;
        }
    
        /**
        * @param string $name
        * @param array  $args
        * @return mixed
        */
    
        public function __call($name, $args)
        {
            if (property_exists($this, $name) && is_callable($this->$name)) {
                return call_user_func_array($this->$name, $args);
            }
        }
    }
    

    Пример


    Вот мой пример применения, он сделан по аналогии с примером товарища Bodigrim
    <?php
    require_once 'ACurry.php';
    
    /**
     * A class to calculate a mass from the density and size
     */
    
    class Masses extends ACurry{
    
        public function __construct(){
            /* create method to calculate mass of iron cube   */
            $this->createMethod('ironCube', 'cube', array(7.8));
        }
    
        /**
         * Method return a mass of subjection from density and size
         */
    
        public function get($density, $length, $width, $height){
            return $density * $length * $width * $height;
        }
    
        /**
         * Method return a mass of cube subjection from density and size
         */
    
        public function cube($density, $length){
            return $this->get($density, $length, $length, $length);
        }
    }
    
    $masses=new Masses();
    echo $masses->ironCube(2);
    

    В данном примере псевдометод ironCube() вычисляет массу железного куба в граммах со стороной 2 см (как известно, у железа плотность 7.8 г/см³).

    Итог


    Вот и получилось этакое объектно-ориентированное функциональное метапрограммирование. Конечно же, область применимости этого приема возрастет в разы, если преобразуем класс в trait, что позволит нам каррировать уже наследованные методы.
    • –1
    • 2.4k
    • 9
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 9

      0
      Коментарии к голосованию, критика и замечания приветствуются
        0
        мой топик уже слили по метапрограммингу))
          +1
          Я читал его. Ничего интересного для себя не почерпнул. Нашел ошибки.
            –1
            Очень жаль, так как для меня только открылось понятие метапрограммирование, и ваш топик мне понравился, не смотря на то что есть много информации по нему. Не могу плюсануть, так как меня слили))
        +2
        И так вот он класс с каррирующим методом, который и реализует (простите за каламбур) частичное использование метода класса. При этом, создается псевдометод, который и вызывается (опять скаламбурил) магическим методом __call():


        Поясните, пожалуйста, в чём здесь заключаются каламбуры?
          +1
          Не понял смысла Вашего подхода. Если у Вас есть возможность добавлять свои методы в класс, то зачем это делать таким способом? В чём выигрыш?
            0
            Что бы познать сакральный смысл поста, для, начала ВНИМАТЕЛЬНО прочтите пост, на который я ссылался вначале, а затем мой. И если вы не сможете понять его, то видимо, Вы просто не на тот путь встали.
              0
              Там берётся существующая(произвольная) функция и каррируется, и получается новая функция. При этом исходная функция не меняется и не знает, что ее каррировали.

              Применительно к объектам вижу два пути. Каррируем метод объекта и получаем…
              1.… новую функцию;
              2.… новый объект с тем же интерфейсом плюс новый метод.

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

              В Вашем же подходе, чтобы каррировать метод объекта нужно вмешаться в иерархию наследования и изменить конструктор.
                0
                Да, я с вами согласен, но не во всем. Функция каррирует функцию, а метод каррирует метод. И по этому если мы каррируем метод, то и получаем мы новый метод (именно метод, а не функцию, как сказано у вас). И каррируемый метод, так же не догадывается, что его каррировали. А так как в PHP нет множественного наследования, то мы не можем наследоваться от двух и более классов, и что бы сделать так, «чтобы ни объект, ни методы не знали о том как с ними обращаются», я в конце топика подытожил, что класс, можно переписать в trait. Да и в конструктор не нужно вмешиваться, т.к. createMethod() имеет область видимости public.

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