Каррирование и частичное применение
Я, оказывается, путал раньше каррирование и частичное применение. Даже не то чтобы путал, просто не пытался разобраться в чём разница между терминами, применяя то и другое по мере надобности. Недавно на «Хабре» в комментариях мне на моё незнание указали и я уяснил, наконец, что конкретно стоит за каждым термином.
Каррирование — преобразование функции от двух аргументов в функцию от первого аргумента, возвращающую функцию, результат вызова которой со вторым агрументом эквивалентен вызову первоначальной функции с упомянутыми аргументами. Немного запутанно, с примерами станет всё яснее. Я буду объяснять на примере Джаваскрипта, его больше народу знает, а на ПХП очень уж многословно получается. Кстати, в соответствующую статью в «Википедии» я добавил пример на «Гугл Гоу».
// функция, суммирующая два числа
function sum(x, y) {
return x+y;
}
alert(sum(2, 8)); // вернёт 10
function sum(x) {
return function (y) {
return x+y;
}
}
alert(sum(2)(8)); // тоже вернёт 10
При вызове второй функции sum создаётся функция, в которую из-за замыкания, «железно» попадает аргумент «x» со значением «2». Поэтому возвращаемая функция будет всегда суммировать с двойкой. С этим фактом тесно связано частичное применение, потому что оно только что, фактически, состоялось.
Частичное применение функции (Partial function) — создание некой функции на базе указанной, где некоторые агрументы заменены конкретными значениями. Например:
function partial_one_arg(f, a) {
return function(b) {
return f(a, b);
}
}
min0 = partial_one_arg(Math.min, 0);
min1 = partial_one_arg(Math.min, 1);
alert([min0(-5), min0(5)]); // вернёт [-5, 0]
alert([min1(-5), min1(5)]); // вернёт [-5, 1]
В примере я заменил один из агрументов фунции нахождения минимального значения на конкретное значение, теперь при вызове функции «min0» сравнение будет идти с нулём, при вызове «min1» — с единицей.
Очень простые штуки, часто встречающиеся в функциональном программировании.
alert([min0(-5), min0(5)]); // вернёт [-5, 0]
alert([min1(-5), min1(5)]); // вернёт [-5, 1]
по крайней мере, у меня в интерпретаторе сейчас так вышло
и разницу я так и не понял, возможно потому, что в одном примере сумма, а в другом Math.min, и в одном примере каррирование делается вручную, а во втором автоматизируется
Комментарий для arty.name:
Да, перепутал немного.
Понятия близкие, я же говорю. В одном случае функция из двух аргументов переводится в две функции одного аргумента, во втором — создаётся функция на основе другой, но с заранее подставленными некоторыми параметрами.
В питоне с версии 2.6 в functools есть функция partial, реализующая частичное применение. С её использованием есть небольшие тонкости. Дело в том, что она возвращает не функцию, а partial object, который похож на функцию. Помимо того, что у такого объекта нет __name__ по умолчанию (можно выставить руками), функция isfunction из модуля inspect (которая для проверки, в свою очередь, использует FunctionType из модуля types) для partial-объекта возвращает False. Отсюда вытекают всякие забавные эффекты, например, partial-функцию нельзя зарегистрировать в качестве фильтра в Django-шаблонах.
Комментарий для ninjacolumbo.ya.ru:
Спасибо, я таких подробностей про неё не знал. Интересно, для чего так сделано?
Комментарий для ninjacolumbo.ya.ru:
Можно сделать quickfix:
return [arg1, arg2]
но всё равно ерунда получается (плохой __name__, нет __doc__), проще partial переписать полностью.
Комментарий для Евгения Степанищева:
Сложно сказать для чего так сделано, partial написан на си — я не стал туда лезть. Но, как говорит Спольски, в данном случае абстракция дала течь %) Callable-объект не может быть использован в качестве функции, причём однозначно сказать, кто виноват: functools, inspect или Django — не получится.
Комментарий для ninjacolumbo.ya.ru:
Ну, как учат нас утки, не надо завязываться на то как объект называется, надо смотреть на то, что он умеет. С этой точки зрения, Джанга не права, вероятно.
Комментарий для ninjacolumbo.ya.ru:
Смотри: