|
| ||||||||||||
| ||||||||||||
|
2003 г
Функциональное программирование на языке PythonАвтор: David Mertz, Ph.D., Applied Metaphysician, Gnosis Software, Inc.Перевод: Яков Маркович, ведущий инженер-исследователь "Intersoft Lab" Хотя пользователи обычно думают о Python как о процедурном и
объектно-ориентированном языке, он содержит все необходимое для поддержки полностью
функционального подхода к программированию. Что такое Python?Python - свободно распространяемый, очень высокоуровневый интерпретируемый язык, разработанный Гвидо ван Россумом (Guido van Rossum). Он сочетает прозрачный синтаксис с мощной (но необязательной) объектно-ориентированной семантикой. Python доступен почти на всех существующих ныне платформах и обладает очень высокой переносимостью между платформами.Что такое функциональное программирование?Лучше всего начать с труднейшего вопроса - а что, собственно, такое "функциональное программирование (FP)"? Один из возможных ответов - "это когда вы пишете на языке наподобие Lisp, Scheme, Haskell, ML, OCAML, Clean, Mercury или Erlang (или еще на некоторых других)". Этот ответ, безусловно, верен, но не сильно проясняет суть. К сожалению, получить четкое мнение о том, что же такое FP, оказывается очень трудно даже среди собственно функциональных программистов. Вспоминается притча о трех слепцах и слоне. Возможно также определить FP, противопоставив его "императивному программированию" (тому, что вы делаете на языках наподобие C, Pascal, C++, Java, Perl, Awk, TCL и на многих других - по крайнее мере, большей частью).Хотя автор всеми силами приветствует советы со стороны тех, кто лучше него знает предмет, он мог бы приблизительно охарактеризовать функциональное программирование как обладающее как минимум несколькими из следующих свойств. В языках, называемых функциональными, хорошо поддерживаются нижеперечисленные подходы, а все прочие подходы поддерживаются плохо или не поддерживаются вовсе:
Функциональные возможности, присущие PythonPython поддерживает большую часть характеристик функционального программирования, начиная с версии Python 1.0. Но, как большинство возможностей Python, они присутствуют в очень смешанном языке. Так же как и с объектно-ориентированными возможностями Python, вы можете использовать то, что вам нужно, и игнорировать все остальное (пока оно вам не понадобится). В Python 2.0 было добавлено очень удачное "синтаксическое украшение" - списочные встраивания (list comprehensions). Хотя и не добавляя принципиально новых возможностей, списочные встраивания делают использование многих старых возможностей значительно приятнее.Базовые элементы FP в Python - функции Исключение команд управления потокомПервое, о чем стоит вспомнить в нашем упражнении - то, что Python "замыкает накоротко" вычисление логических выражений.1 Оказывается, это предоставляет эквивалент блока'if'/'elif'/'else' в виде выражения. Итак:
#------ "Короткозамкнутые" условные вызовы в Python -----#Казалось бы, наша версия условных вызовов с помощью выражений - не более, чем
салонный фокус; однако все становится гораздо интересней, если учесть, что оператор
#--------- Lambda с короткозамкнутыми условными выражениями в Python -------#Функции как объекты первого классаПриведенные примеры уже засвидетельствовали, хотя и неочевидным образом, статус функций как объектов первого класса в Python. Дело в том, что, создав объект функции операторомlambda, мы произвели чрезвычайно общее действие. Мы имели
возможность привязать наш объект к именам pr и namenum в точности тем же способом,
как могли бы привязать к этим именам число 23 или строку "spam". Но
точно так же, как число 23 можно использовать, не привязывая ни к какому имени
(например, как аргумент функции), мы можем использовать объект функции, созданный
lambda, не привязывая ни к какому имени. Функция в Python - всего лишь еще одно
значение, с которым можно что-то сделать. Главное, что мы делаем с нашими объектами первого класса - передаем их во встроенные
функции Комбинируя три этих встроенных FP-функции, можно реализовать неожиданно широкий диапазон операций потока управления, не прибегая к утверждениям (statements), а используя лишь выражения. Функциональные циклы в PythonЗамена циклов на выражения так же проста, как и замена условных блоков.'for'
может быть впрямую переведено в map(). Так же, как и с условным выполнением,
нам понадобится упростить блок утверждений до одного вызова функции (мы близки
к тому, чтобы научиться делать это в общем случае):
#---------- Функциональный цикл 'for' в Python ----------#Кстати, похожая техника применяется для реализации последовательного выполнения программы, используя функциональный подход. Т.е., императивное программирование по большей части состоит из утверждений, требующих "сделать это, затем сделать то, затем сделать что-то еще". 'map()' позволяет это выразить так: #----- Функциональное последовательное выполнение в Python ----------#В общем случае, вся главная программа может быть вызовом 'map()' со списком функций, которые надо последовательно вызвать, чтобы выполнить программу. Еще одно удобное свойство функций как объектов - то, что вы можете поместить их в список. Перевести 'while' впрямую немного сложнее, но вполне получается : #-------- Функциональный цикл 'while' в Python ----------#Наш вариант #---------- Функциональный цикл 'echo' в Python ------------#Мы достигли того, что выразили небольшую программу, включающую ввод/вывод,
циклы и условия в виде чистого выражения с рекурсией (фактически - в виде функционального
объекта, который при необходимости может быть передан куда угодно). Мы все еще
используем служебную функцию Исключение побочных эффектовПосле всей проделанной работы по избавлению от совершенно осмысленных конструкций и замене их на невразумительные вложенные выражения, возникает естественный вопрос - "Зачем?!". Перечитывая мои описания характеристик FP, мы можем видеть, что все они достигнуты в Python. Но важнейшая (и, скорее всего, в наибольшей степени реально используемая) характеристика - исключение побочных эффектов или, по крайней мере, ограничение их применения специальными областями наподобие монад. Огромный процент программных ошибок и главная проблема, требующая применения отладчиков, случается из-за того, что переменные получают неверные значения в процессе выполнения программы. Функциональное программирование обходит эту проблему, просто вовсе не присваивая значения переменным.Взглянем на совершенно обычный участок императивного кода. Его цель - распечатать список пар чисел, чье произведение больше 25. Числа, составляющие пары, сами берутся из двух других списков. Все это весьма напоминает то, что программисты реально делают во многих участках своих программ. Императивный подход к этой задаче мог бы выглядеть так: #--- Императивный код для "печати произведений" ----#Этот проект слишком мал для того, чтобы что-нибудь пошло не так. Но, возможно,
он встроен в код, предназначенный для достижения множества других целей в то
же самое время. Секции, комментированные как Функциональный подход к нашей задаче полностью исключает ошибки, связанные с побочными эффектами. Возможное решение могло бы быть таким: #--- Функциональный код для поиска/печати больших произведений на Python ----#Мы связываем в примере анонимные ( Реальное преимущество этого функционального примера в том, что в нем абсолютно
ни одна переменная не меняет своего значения. Какое-либо неожиданное побочное
влияние на последующий код (или со стороны предыдущего кода) просто невозможно.
Конечно, само по себе отсутствие побочных эффектов не гарантирует безошибочность
кода, но в любом случае это преимущество. Однако заметьте, что Python, в отличие
от многих функциональных языков, не предотвращает повторное привязывание имен
bigmuls, combine и dupelms. Если дальше в процессе выполнения программы Еще стоит отметить, что задача, которую мы только что решили, скроена в точности под новые возможности Python 2.0. Вместо вышеприведенных примеров - императивного или функционального - наилучшая (и функциональная) техника выглядит следующим образом: #----- Код Python для "bigmuls" с использованием списочных встраиваний
(list comprehensions) -----#ЗаключениеЭта статья продемонстрировала способы замены практически любой конструкции управления потоком в Python на функциональный эквивалент (избавляясь при этом от побочных эффектов). Эффективный перевод конкретной программы требует дополнительного обдумывания, но мы увидели, что встроенные функциональные примитивы являются полными и общими. В последующих статьях мы рассмотрим более мощные подходы к функциональному программированию; и, я надеюсь, сможем подробнее рассмотреть "pro" и "contra" функционального подхода.РесурсыБиблиотека "xoltar toolkit" Брина Келлера (Bryn Keller), включающий модуль [functional], добавляет множество полезных FP-расширений к Python. Поскольку сам модуль [functional] написан на чистом Python, то, что он делает, можно сделать и без него. Но Келлер создал замечательно интегрированный набор расширений, предоставляющий высокую мощность в компактном определении. Пакет можно найти по адресу: http://sourceforge.net/projects/xoltar-toolkitПитер Норвиг (Peter Norvig) написал интересную статью, "Питон для программистов на Лиспе" ("Python for Lisp Programmers"). Несмотря на то, что статья в основном сфокусирована на вопросах, противоположных только что рассмотренным, в ней проводится отличное общее сравнение Python и Lisp: http://www.norvig.com/python-lisp.html
Автор нашел, что понять суть функционального программирования много проще через язык Haskell, нежели через Lisp (несмотря на то, что последний, вероятно, используется шире - хотя бы в Emacs). Возможно, другим программистам на Python тоже окажется легче жить без такого количества скобок и префиксной (польской) записи. Блестящее введение в язык: Haskell: The Craft of Functional Programming (2nd Edition), Simon Thompson, Addison-Wesley (1999). Об авторе{Изображение автора: http://gnosis.cx/cgi/img_dqm.cgi}Поскольку постижение без интуиции бесплодно, а интуиция без постижения слепа,
Давид Мертц хочет поставить литую скульптуру Мильтона в свой офис. Запланируйте
подарить ему на день рождения. Примечания1. T.е. вычисление логического выражения заканчивается сразу, как только становится известен его логический результат. Стоит также заметить, что в Python, так же как и в Lisp, значением логического выражения является не true/false, а значение последнего вычисленного подвыражения - например, 4 and "Hello!" or 2*2 будет иметь значение "Hello!". прим. перев.2. monadic_print() может быть реализована в полностью функциональном стиле, без использования утверждения print: monadic_print = lambda x: sys.write(str(x) + '\n') and xecho_FP глобальна. Это связано с тем, что в Python всех версий до
2.0 включительно отсутствует статическая вложенность области действия имен.
Любое имя, встретившееся в контексте функции или метода, ищется сначала среди
локальных имен функции, а потом сразу среди глобальных имен (затем среди встроенных
имен). Это отличается от логики языков со статическими областями действия имен
(C, C++, Pascal, etc.), где имя последовательно ищется во всех объемлющих блоках.
Из этого, в частности, следует, что рекурсивный вызов lambda-функции, привязанной
к неглобальному имени, в Python версии меньше 2.1 невозможен. В Python 2.1 введены
опциональные статические области действия. Таким образом, начиная с версии 2.1
Python можно рассматривать и как полноценный FP-язык (помимо всего прочего).
Вышеприведенный комментарий относится почти ко всем функциональным примерам
в статье. прим.
Оригинальный текст статьи можно посмотреть здесь: |
|
CITForum © 1997–2025