Функции высшего порядка: частичное вычисление функций - карринг (currying)
Три наиболее общих функций высшего порядка встроены в Python: map(), reduce()
и filter(). Эти функции используют в качестве (некоторых) своих параметров другие функции - вот почему мы называем их функциями высшего порядка. Другие функции высшего порядка (но не эти три) возвращают объекты-функции (function objects).
Python всегда предоставлял программистам возможность создавать свои собственные функции высшего порядка благодаря полноправному статусу функций как объектов. Ниже в качестве иллюстрации приведен простой пример:
#----------- Trivial Python function factory ------------#
>>> def foo_factory():
... def foo():
... print "Foo function from factory"
... return foo
...
>>> f = foo_factory()
>>> f()
Foo function from factory
Программа Xoltar Toolkit, о которой я упоминал в предыдущих статьях, содержит замечательный набор функций высшего порядка. Большинство этих функций, предоставляемых модулем functional, имеются во множестве традиционных функциональных языках программирования, и их полезность проверена многолетним использованием. Пожалуй, наиболее известная и важная функция высшего порядка традиционно называется curry(). Она названа в честь логика Хаскелла Карри (Haskell Curry), чьим именем назван уже упоминавшийся язык программирования. В основе карринга лежит допущение о том, что (почти) любую функцию можно рассматривать как частично вычисляемую функцию одного аргумента. Для того, чтобы эта идея работала, необходимо чтобы значение, возвращаемое функцией, само могло быть функцией, но возвращаемые функции должны быть уже или ближе к завершению .
Этот механизм подобен замыканию, о котором я рассказывал в предыдущей статье - каждый вызов каррированой функции добавляет больше данных, необходимых для окончательного вычисления (данные прикрепляются к процедуре). Проиллюстрируем сначала карринг очень простым примером на Haskell, а затем повторим тот же пример на Python с помощью модуля functional:
#------------- Currying a Haskell computation -----------#
computation a b c d = (a + b^2+ c^3 + d^4)
check = 1 + 2^2 + 3^3 + 5^4
fillOne = computation 1 -- specify "a"
fillTwo = fillOne 2 -- specify "b"
fillThree = fillTwo 3 -- specify "c"
answer = fillThree 5 -- specify "d"
-- Result: check == answer == 657
А теперь на Python:
#------------- Currying a Python computation ------------#
>>> from functional import curry
>>> computation = lambda a,b,c,d: (a + b**2 + c**3 + d**4)
>>> computation(1,2,3,5)
657
>>> fillZero = curry(computation)
>>> fillOne = fillZero(1) # specify "a"
>>> fillTwo = fillOne(2) # specify "b"
>>> fillThree = fillTwo(3) # specify "c"
>>> answer = fillThree(5) # specify "d"
>>> answer
657
Приведем еще один пример, подтверждающий, что между каррингом и замыканием много общего. Для этого, используя curry(), перепишем простую программу расчета налога, код которой можно найти в предыдущей статье:
#------------ Python curried tax calculations -----------#
from functional import *
taxcalc = lambda income,rate,deduct: (income-(deduct))*rate
taxCurry = curry(taxcalc)
taxCurry = taxCurry(50000)
taxCurry = taxCurry(0.30)
taxCurry = taxCurry(10000)
print "Curried taxes due =",taxCurry
print "Curried expression taxes due =", \
curry(taxcalc)(50000)(0.30)(10000)
В отличие от замыкания, при использовании curry( ) необходимо заполнять параметры в определенном порядке (слева направо). Но заметьте, в модуль functional также включен класс rcurry(), для которого отсчет начинается с другого конца (справа налево).
Обратите внимание на второй оператор print
в этом примере - с одной стороны, это всего лишь тривиальное синтаксическое изменение - можно было бы просто вызвать taxcalc(50000,0.30,10000). Но с другой стороны, благодаря этому становится понятным идея о том, что каждая функция может быть функцией всего одного аргумента - весьма неожиданная идея для тех, кто с эти незнаком.