|
| ||||||||||||
| ||||||||||||
|
2003 г
И опять о функциональном программировании на PythonDavid Mertz, Ph.D., Applied Metaphysician, Gnosis Software, IncПеревод: Intersoft Lab Предыдущие статьи коснулись основных понятий функционального программирования (ФП). Эта статья продолжит обсуждение, иллюстрируя дополнительные возможности, главным образом реализованные в библиотеке Xoltar Toolkit: частичное вычисление функций (Currying, карринг), функции высшего порядка (higher-order functions) и другие концепции. Что такое python?Python - свободно доступный, интерпретируемый язык программирования высокого уровня, разработанный Гвидо ван Россумом (Guido van Rossum). Он объединяет ясный синтаксис с мощной (но необязательно) объектно-ориентированной семантикой. Python может быть установлен на любой платформе и обеспечивает прекрасную совместимость при переходе с одной платформы на другую. Связывание выраженийНедовольный полурешениями, один из читателей - Ричарда Дейвис (Richard Davies) - поднял вопрос, можем ли мы целиком переместить связывания в отдельные выражения. Давайте попытаемся понять, зачем нам может этого захотеться, а также продемонстрируем замечательно элегантный способ этого добиться, предоставленный участником comp.lang.python. Давайте сначала вспомним о классе Bindings, определенном в модуле functional. Используя свойства этого класса, мы смогли гарантировать, что отдельное имя имеет единственное значение в пределах области данного блока:
>>> from functional import * >>> let = Bindings() >>> let.car = lambda lst: lst[0] >>> let.car = lambda lst: lst[2] Traceback (innermost last): File "", line 1, in ? File "d:\tools\functional.py", line 976, in __setattr__ raise BindingError, "Binding '%s' cannot be modified." % name functional.BindingError: Binding 'car' cannot be modified. >>> let.car(range(10)) 0 С помощью класса Bindings нам удалось достичь желаемого результата в пределах модуля или функции, но в отношении отдельного выражения мы бессильны. Тем не менее, для семейства ML-языков вполне естественно создавать связывания в пределах отдельного выражения:
#-------- Haskell expression-level name bindings --------#Грэг Эвинг (Greg Ewing) заметил, что мы можем достичь того же эффекта, воспользовавшись списочными встраиваниями Python (list comprehensions); мы даже можем сделать это почти столь же ясным способом, как в Haskell:
>>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]] >>> [car_x for x in list_of_list for car_x in (x[0],)] [1, 4, 7] Этот прием - размещение выражения внутри одноэлементного кортежа в списочном встраивании - не позволяет использовать связывание на уровне выражений с функциями высшего порядка. Для использования таких функций мы все еще должны использовать область блока:
>>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]] >>> let = Bindings() >>> let.car = lambda l: l[0] >>> map(let.car,list_of_list) [1, 4, 7] Неплохо, хотя если мы хотим использовать map(), область связывания остается несколько шире, чем мы того хотели. Тем не менее, можно уговорить списочное встраивание делать для нас связывание имен, даже если список - не то, что нам нужно в конечном счете:
# Compare Haskell expression: # result = func car_car # where # car (x:xs) = x # car_car = car (car list_of_list) # func x = x + x^2 >>> [func for x in list_of_list ... for car in (x[0],) ... for func in (car+car**2,)][0] 2 В этом примере мы произвели арифметическое действие над первым элементом первого элемента списка list_of_list и одновременно поименовали это действие (но только в области объемлющего выражения). В качестве "оптимизации" можно посоветовать создавать список длиной не более одного элемента, поскольку с помощью индекса [0] в конце выражения выбираем только первый элемент:
>>> [func for x in list_of_list[:1] ... for car in (x[0],) ... for func in (car+car**2,)][0] 2 Функции высшего порядка: частичное вычисление функций - карринг[1] (currying)Три наиболее общих функций высшего порядка встроены в Python: map(), reduce() и filter(). Эти функции используют в качестве (некоторых) своих параметров другие функции - вот почему мы называем их функциями высшего порядка. Другие функции высшего порядка (но не эти три) возвращают объекты-функции (function objects). Python всегда предоставлял программистам возможность создавать свои собственные функции высшего порядка благодаря полноправному статусу функций как объектов. Ниже в качестве иллюстрации приведен простой пример:
>>> 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 -----------#А теперь на Python:
>>> 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 -----------#В отличие от замыкания, при использовании curry( ) необходимо заполнять параметры в определенном порядке (слева направо). Но заметьте, в модуль functional также включен класс rcurry(), для которого отсчет начинается с другого конца (справа налево). Обратите внимание на второй оператор Другие функции высшего порядкаПомимо фундаментальной функции curry(), в модуль functional включает ряд интереснейших функций высшего порядка. Более того, не составляет большого труда самому написать свои собственные функции высшего порядка - с помощью или без этого модуля. Во всяком случае, можно воспользоваться идеями, представленными в нем. По большей части функции высшего порядка ведут себя как "усовершенствованные" версии стандартных функций map(), filter() и reduce(). В большинстве случаев они действуют согласно следующему правилу: "принять функцию или функции и некоторые списки в качестве параметров, затем применить функцию (функции) к списку параметров". Это правило предоставляет потрясающие возможности для программирования. Другой принцип "принять набор функций и создать функцию, комбинирующую их функциональность". И опять-таки, возможны многочисленные вариации. Давайте взглянем, что предоставляет functional. Функции sequential() и also() создают функцию, основанную на последовательность других функций. Функции-компоненты затем могут быть вызваны с одинаковым аргументом (аргументами). Главное различие между этими двумя функциями заключается в том, что в sequential() список функций принимается в качестве первого аргумента, а also() принимает список аргументов (каждый из которых должен быть функцией) переменной длины. В большинстве случаев их используют ради побочных эффектов составляющих функций, однако sequential() позволяет опционально задать, результат какой функции вернуть как комбинированное значение:
>>> def a(x): ... print x, ... return "a" ... >>> def b(x): ... print x*2, ... return "b" ... >>> def c(x): ... print x*3, ... return "c" ... >>> r = also(a,b,c) >>> r >>> r(5) 5 10 15 'a' >>> sequential([a,b,c],main=c)('x') x xx xxx 'c' Функции disjoin() и conjoin() схожи с sequential() и also() в том смысле, что они также создают новые функции, которые применяют параметр(ы) к нескольким составляющим функциям. Но disjoin() выясняет, возвращает ли хотя бы одна из составляющих функций "истину" (true), а conjoin() выясняет, возвращают ли все функции "истину". При этом, когда это возможно, логика "короткого замыкания"[2], поэтому при их вызове часть побочных эффектов может не проявиться. joinfuncs() похожа на also(), но, в отличие от нее, возвращает кортеж результатов составляющих функций, а не выбирает одно значение. В то время как вышеуказанные функции вызывают много функций с одинаковыми параметрами, функции any(), all() и none_of() позволяют вызывать одну и ту же функцию для каждого значения из списка. В общем случае они подобны встроенным функциям map(), filter() и reduce(). Но, в отличие от последних, эти функции задают булевы (логические) вопросы касательно набора возвращаемых величин. Например,
>>> from functional import * >>> isEven = lambda n: (n%2 == 0) >>> any([1,3,5,8], isEven) 1 >>> any([1,3,5,7], isEven) 0 >>> none_of([1,3,5,7], isEven) 1 >>> all([2,4,6,8], isEven) 1 >>> all([2,4,6,7], isEven) 0 Особый интерес для тех, кто неравнодушен к математике, представляет функция высшего порядка compose(). Композиция из нескольких функций формирует цепочку, направляя возврат одной функции на вход следующей. Программист, комбинирующий несколько функций, должен следить за тем, чтобы выход и вход соответствовали друг другу - но ведь это так в любом случае, если программист использует возвращаемое значение. Ниже приведен пример, поясняющий сказанное:
>>> def minus7(n): return n-7 ... >>> def times3(n): return n*3 ... >>> minus7(10) 3 >>> minustimes = compose(times3,minus7) >>> minustimes(10) 9 >>> times3(minus7(10)) 9 >>> timesminus = compose(minus7,times3) >>> timesminus(10) 23 >>> minus7(times3(10)) 23 Некоторые рекомендацииНадеюсь, этот обзор функций высшего порядка пробудит у читателя интерес к определенному стилю мышления. Во всяком случае, поэкспериментируйте с ними. Попытайтесь создать собственные функции высшего порядка; некоторые из них могут оказаться весьма полезными и мощными. Поделитесь со мной своими достижениями, и, возможно, в будущей статье мы обсудим оригинальные и замечательные идеи, которыми непрерывно снабжают меня читатели. РесурсыПредыдущие статьи, посвященные программированию на языке Python можно найти по следующему адресу: http://gnosis.cx/publish/programming/charming_python_13.htmlhttp://gnosis.cx/publish/programming/charming_python_16.html Библиотека Xoltar toolkit, написанная Брином Келлером и включающая модуль functional, значительно расширяет возможности ФП на Python. Поскольку модуль functional написан на чистом Python, все, что он делает так или иначе уже возможно в Python. Но Келлер создал очень удачный комплект расширений, предоставлющий большую мощность при компактности определения. Библиотеку можно найти по адресу: http://sourceforge.net/projects/xoltar-toolkitЕсли вы только начали изучать функциональное программирование, вы сможете найти ответы на многие вопросы по адресу: http://www.cs.nott.ac.uk/~gmh//faq.html#functional-languagesАвтор находит, что гораздо легче понять суть функционального программирования, используя язык Haskell, а не Lisp/Scheme (несмотря на то, что последний чаще используется, хотя бы в Emacs). Кроме того, программистам на Python будет много проще жить без такого количества круглых скобок и префиксной (польской) записи: http://www.haskell.org/Прекрасная вводная книга: Haskell: The Craft of Functional Programming (2nd Edition) Еще одна книга более прикладного характера, которая одновременно является отличным введением в язык Haskell: The Haskell School of Expression: Learning Functional Programming
Through Multimedia Об авторе(Фотография автора: http://gnosis.cx/cgi-bin/img_dqm.cgi) Поскольку постижение без интуиции бесплодно, а интуиция без постижения слепа, Дэйвид Мертц хочет литой бюст Джона Мильтона в свой офис. Запланируйте подарить ему на день рождения. Дэйвид доступен по адресу: mertz@gnosis.cx, а жизнь его описана на http://gnosis.cx/publish/. Присылайте свои замечания и предложения касательно этой, прошлых или будущих статей.
[1] Переводчик отдает себе отчет в странности термина "карринг" для русского уха. Но в большей части русскоязычной литературы по Haskell он звучит именно так. (Прим. перев.) [2] Т.е. вычисления прекращаются, как только известен результат. Для disjoin() первый возврат true приведет к прекращению вычислений; для conjoin() - false. (Прим. перев.)
Оригинальный текст статьи можно посмотреть
здесь:
Even More Functional Programming in Python |
|
CITForum © 1997–2025