(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Eще раз о каррировании и частичном применении в PHP

Источник: habrahabr
Bodigrim

Искусство каррирования

В недавней статье предложена реализация каррирования (currying) и частичного применения (partial function application) на PHP. Ее фундаментальным недостатком является то, что результатом каррирования является не функция, а объект. Он уже не может быть передан в качестве callback-параметра, а для подстановки аргументов приходится использовать специальный синтаксис. В настоящем тексте предлагается новая, прозрачная реализация этих конструкций для PHP 5.3 и выше.

Термин currying происходит от фамилии американского математика Haskell Curry. Второе значение слова currying - выделка дубленой кожи. 

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

Эмуляция каррирования и частичного применения на PHP - это один из примеров того, что Макконнелл в "Совершенном коде" (гл. 4.3) называет программированием с использованием языка, а не на языке.

Краткий ликбез


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

Например, пусть у нас есть "черный ящик" - функция solve(f, x0, ε), находящая решение уравнения f(x) = 0 в окрестности начальной точки x0 с точностью ε. Тогда при помощи вызова частичного применения мы можем построить функцию solve1(x0, ε) ≡ solve(x − tg x, x0, ε). Или даже функцию solve2(ε), которая решала бы некое фиксированное уравнение в окрестности фиксированной начальной точки с переменной точностью.

Разумеется, в каждом частном случае мы можем написать функцию-обертку типа
function solve_x_minus_tan_x($x0, $eps){
    $f = function($x) { return $x - tan($x); };
    return solve($f, $x0, $eps);
}

однако было бы удобнее иметь универсальный механизм, которым и является частичное применение. 

Каррирование - это процедура, преобразующая функцию от n переменных в цепочку из n функций одной переменной, выполняя поочередную подстановку аргументов. Например, пусть add(a, b) = a + b, a curry_add - результат каррирования функции add. Тогда вызов curry_add(a) для каждого a будет порождать функции одного аргумента, прибавляющие к нему a, т. е. curry_add(a)(b) = add(a, b). Больше примеров будет приведено ниже.

Детальнее с каррированием и частичным применением можно ознакомиться в большой статье Е. Кирпичева "Элементы функциональных языков" (раздел 5).

Каррирующая функция


Итак, слайды. Все, что нам нужно, - это следующий код, который заменяет исходную функцию ее каррированной версией. 
function curry($callback, $args = array()){
/* $callback - исходная функция
   $args     - массив ее аргументов, если они уже определены */

    /* строим каррированную функцию */
    $ret = function() use($callback, $args){
    
        /* определяем число аргументов исходной функции */
        $func = new ReflectionFunction($callback);
        $num = $func->getNumberOfParameters();
    
        /* добавляем новые аргументы к уже имеющемуся набору */ 
        $args = array_merge($args, func_get_args());
        
        /* если уже набралось необходимое число аргументов, */
        if(count($args) >= $num){
            /* то подставляем их в исходную функцию 
               и возвращаем результат вычисления */
            return call_user_func_array($callback, $args);
        }
        /* если же аргументов меньше, чем необходимо, */
        else {
            /* то рекурсивно вызываем каррирование исходной функции
               с более полным набором аргументов */
            return curry($callback, $args);
        }
    };
        
    return $ret;
}

Классический пример


Пусть у нас определена функция add.
function add($a, $b) { return $a + $b; }

Мы можем построить ее каррированную версию.
$add = curry("add");

Проверим, что полученная функция ведет себя так же, как и исходная.
echo $add(2, 5); // выведет 7

Теперь подставим только первый аргумент, чтобы сгенерировать функции инкремента и декремента.
$inc = $add(1);
$dec = $add(-1);
echo $inc(6); // выведет 7
echo $dec(8); // выведет 7

Еще примеры


Мы можем совершенно прозрачно подставлять произвольное количество начальных аргументов и получать полноценную функцию, в которую можно снова подставить не все аргументы. Например, 
function add_and_mul($a, $b, $c) { return $a + $b * $c; }
    
$add_and_mul = curry("add_and_mul");

$test1 = $add_and_mul(1, 2, 3); // просто значение функции
$test2 = $add_and_mul(1, 2);    // функция одного аргумента
$test3 = $add_and_mul(1);       // функция двух аргументов
$test4 = $test3(2);             // функция одного аргумента

// все следующие строки выводят 7
echo $test1;
echo $test2(3);
echo $test3(2, 3);
echo $test4(3);

Результат каррирования можно без проблем передать в качестве callback-параметра другой функции. Например, пусть нам надо вычислить массы кубов по плотности и массиву длин сторон. Мы можем сделать это следующим образом.
/* функция, вычисляющая массу по плотности и длине стороны куба */
function mass($density, $length){ return $density * $length * $length * $length; }

/* каррируем */
$mass = curry("mass");

/* функция, вычисляющая массу стального куба */
$steel_mass = $mass(7.9);

/* массив длин сторон кубов */
$lengths = array(3, 2, 5, 6, 1);

/* вычисляем массы кубов */
$masses = array_map($steel_mass, $lengths);

/* выведет Array ( [0] => 213.3 [1] => 63.2 [2] => 987.5 [3] => 1706.4 [4] => 7.9 ) */
print_r($masses); 

Примечания


  1. C точки зрения интерпретатора PHP результат нашего каррирования является не функцией, а объектом класса Closure, поскольку построен как анонимное замыкание. Однако с точки зрения синтаксиса подмена совершенно прозрачна.
  2. По очевидным причинам нельзя каррировать функции с переменным числом аргументов типа printf(). В нашей реализации все аргументы функции становятся обязательными, даже если в исходной сигнатуре они были помечены как необязательные. Также следует отметить, что при попытке посчитать количество аргументов каррированной функции getNumberOfParameters() вернет 0.
  3. Строго говоря, каррированная функция должна принимать аргументы по одному, т. е. вместо $add(2, 5) надо писать $add(2)(5). Однако текущая версия интерпретатора PHP считает записи типа func(arg1)(arg2) синтаксической ошибкой, даже если семантически они верны. Поэтому для удобства наша реализация позволяет указывать сразу несколько аргументов через запятую, что сближает ее с частичным применением.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 07.08.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft 365 Apps for business (corporate)
Delphi Professional Named User
Microsoft 365 Business Standard (corporate)
Microsoft Office для дома и учебы 2019 (лицензия ESD)
Microsoft Windows Professional 10, Электронный ключ
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
Работа в Windows и новости компании Microsoft
Windows и Office: новости и советы
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100