Карринг в Питоне
Карринг — термин из λ-исчисления. Назван по имени Хаскелла Карри и обозначает операцию преобразования функции многих переменных в функцию одной переменной, которая возвращает функцию n-1 переменной. Это если упрощённо. Если сложнее, то она задаёт операцию развёртки n-арной λ-функции на цепочку унарных, при n > 1.
В качестве тренировки решил написать свою функцию-декоратор на Питоне, реализующую карринг.
Собственно она:
def curry(n):
def realcurry(func):
if n < 2:
return func
else:
return lambda a: curry(n-1)(lambda *args: func(a, *args))
return realcurry
Использование:
@curry(5)
def calc(a, b, c, d, e):
return a+b*c+d-e
print calc(2)(2)(2)(2)(2)
class TestClass(object):
@curry(6)
def calc(self, a, b, c, d, e):
return a+b*c+d-e
obj = TestClass()
print obj.calc()(2)(2)(2)(2)(2)
Кратко как оно работает: функция curry()
рекурсивно создаёт цепочку λ-функций одного аргумента, которые последовательно вызывают друг друга вплоть до момента, когда число аргументов достигает одного. Работает это благодаря счётчику глубины развёртки n, который попадает в замыкание в функции realcurry()
.
Можно было бы использовать анализ *args на длину и избежать использования отдельного счётчика, но тогда принимать решение о конце рекурсии пришлось бы уже в самой внутренней λ-функции-обёртке, то есть во всей длинющей цепочке функций на каждый вызов добавилось бы ещё по одному выражению if, что не есть хорошо. Кроме того явное задание глубины развёртки придаёт такому подходу дополнительную гибкость.
Ещё стоит заметить, что при вызове развёрнутого метода надо дополнительно делать первый вызов без аргументов. На самом деле при этом в метод неявно передаётся ссылка на инстанс класса self. Если этого вызова не будет, то метод не получит этой ссылки и не сможет нормально отработать. В принципе это можно учесть либо в самом декораторе curry()
, либо добавив ещё один декоратор дополнительно преобразовывающий скаррированный метод объекта. Пусть это будет домашним заданием читателя: меня в этом решении пока всё устраивает =)