Это сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

Каррирование и частичное применение

Я, оказывается, путал раньше каррирование и частичное применение. Даже не то чтобы путал, просто не пытался разобраться в чём разница между терминами, применяя то и другое по мере надобности. Недавно на «Хабре» в комментариях мне на моё незнание указали и я уяснил, наконец, что конкретно стоит за каждым термином.

Каррирование — преобразование функции от двух аргументов в функцию от первого аргумента, возвращающую функцию, результат вызова которой со вторым агрументом эквивалентен вызову первоначальной функции с упомянутыми аргументами. Немного запутанно, с примерами станет всё яснее. Я буду объяснять на примере Джаваскрипта, его больше народу знает, а на ПХП очень уж многословно получается. Кстати, в соответствующую статью в «Википедии» я добавил пример на «Гугл Гоу».

// функция, суммирующая два числа
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» — с единицей.

Очень простые штуки, часто встречающиеся в функциональном программировании.

8 комментариев
Artemy Tregubenko (arty.name) 2012

alert([min0(-5), min0(5)]); // вернёт [-5, 0]
alert([min1(-5), min1(5)]); // вернёт [-5, 1]

по крайней мере, у меня в интерпретаторе сейчас так вышло

и разницу я так и не понял, возможно потому, что в одном примере сумма, а в другом Math.min, и в одном примере каррирование делается вручную, а во втором автоматизируется

Евгений Степанищев (bolknote.ru) 2012

Комментарий для arty.name:

Да, перепутал немного.

и разницу я так и не понял, возможно потому, что в одном примере сумма, а в другом Math.min, и в одном примере каррирование делается вручную, а во втором автоматизируется

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

ninjacolumbo (ninjacolumbo.ya.ru) 2012

В питоне с версии 2.6 в functools есть функция partial, реализующая частичное применение. С её использованием есть небольшие тонкости. Дело в том, что она возвращает не функцию, а partial object, который похож на функцию. Помимо того, что у такого объекта нет __name__ по умолчанию (можно выставить руками), функция isfunction из модуля inspect (которая для проверки, в свою очередь, использует FunctionType из модуля types) для partial-объекта возвращает False. Отсюда вытекают всякие забавные эффекты, например, partial-функцию нельзя зарегистрировать в качестве фильтра в Django-шаблонах.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для ninjacolumbo.ya.ru:

Спасибо, я таких подробностей про неё не знал. Интересно, для чего так сделано?

Евгений Степанищев (bolknote.ru) 2012

Комментарий для ninjacolumbo.ya.ru:

Можно сделать quickfix:

import functools
def a(arg1, arg2):

        return [arg1, arg2]

b = lambda *args, kargs: functools.partial(a, 1)(*args, kargs)
print type(b), b.__name__, b(2)

но всё равно ерунда получается (плохой __name__, нет __doc__), проще partial переписать полностью.

ninjacolumbo (ninjacolumbo.ya.ru) 2012

Комментарий для Евгения Степанищева:

Сложно сказать для чего так сделано, partial написан на си — я не стал туда лезть. Но, как говорит Спольски, в данном случае абстракция дала течь %) Callable-объект не может быть использован в качестве функции, причём однозначно сказать, кто виноват: functools, inspect или Django — не получится.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для ninjacolumbo.ya.ru:

Ну, как учат нас утки, не надо завязываться на то как объект называется, надо смотреть на то, что он умеет. С этой точки зрения, Джанга не права, вероятно.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для ninjacolumbo.ya.ru:

Смотри:

In [1]: import functools
In [2]: s = functools.partial(sum, [1])
In [3]: callable(s)
Out[3]: True