Откуда идут «функциональные» корни Python

http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html
  • Перевод
Я никогда не полагал, что Python попадет под влияние функциональных языков, независимо от того что люди говорят или думают. Я знаком с императивными языками, такими как C и Algol68 и хотя я сделал функции объектами «первого класса», я не рассматривал Python как язык функционального программирования. Однако, было ясно, что пользователи хотят больше от списков и функций.

Операции над списками применялись к каждому элементу списка и создавали новый список. Например:

def square(x):
    return x*x

vals = [1, 2, 3, 4]
newvals = []
for v in vals:
    newvals.append(square(v))

На функциональных языках, таких как Lisp и Scheme, операции, такие как эта были разработаны как встроенные функции языка. Таким образом пользователи, знакомые с такими языками, реализовывали аналогичную функциональность в Python. Например:

def map(f, s):
    result = []
    for x in s:
            result.append(f(x))
    return result

def square(x):
    return x*x

vals = [1, 2, 3, 4]
newvals = map(square,vals)

Тонкость вышеупомянутого кода в том, что многим людям не нравился факт, что функция применяется к элементам списка как набор отдельных функций. Языки, такие как Lisp позволили функциям определяться «налету» при маппинге. Например, в Scheme Вы можете создавать анонимные функции и выполнить маппинг в единственном выражении, используя лямбду, как ниже:

(map (lambda (x) (* x x)) '(1 2 3 4)) 

Хотя в Python функции были объектами «первого класса», у него не было никакого подобного механизма для того, чтобы создавать анонимные функции.

В конце 1993, пользователи метались вокруг различных идей, чтобы создать анонимные функции. Например, Марк Луц написал код функции, которая создает функции, используя exec:

def genfunc(args, expr):
    exec('def f(' + args + '): return ' + expr)
    return eval('f')

# Sample usage
vals = [1, 2, 3, 4]
newvals = map(genfunc('x', 'x*x'), vals)

Тим Петерс написал решениерешения, которое упростило синтаксис, разрешая пользователям следующее:

vals = [1, 2, 3, 4]
newvals = map(func('x: x*x'), vals)


Стало ясно, что действительно появилось необходимость в такой функциональности. Одновременно, это казалось заманчиво — определить анонимные функции как строки кода, которые программист должен вручную обработать через exec. Таким образом, в январе 1994, map(), filter(), и reduce() функции были добавлены к стандартной библиотеке. Кроме того, был представлен оператор лямбды для того, чтобы создавать анонимные функции (как выражения) в более прямом синтаксисе. Например:

vals = [1, 2, 3, 4]
newvals = map(lambda x:x*x, vals)

Эти дополнения были существенными на ранней стадии разработки. К сожалению, я не помню автора, и логи SVN не записали его. Если это Ваша заслуга, оставьте комментарий!

Мне никогда не нравилось использовать терминологию «лямбды», но из-за отсутствия лучшей и очевидной альтернативы, она была принята в Python. В конце концов это был выбор теперь анонимного автора, и в то время большие изменения требовали намного меньшего количества обсуждений чем в настоящее время.

Лямбда была предназначена действительно только, чтобы быть синтаксическим сахаром для определения анонимных функций. Однако, у такого выбора терминологии было много непреднамеренных последствий. Например, пользователи, знакомые с функциональными языками, ожидали, что семантика лямбды будет соответствовать онной из других языков. В результате они нашли, что реализации Python очень недоставало расширенного функционала. Например, проблема с лямбдой состоит в том, что предоставленное выражение не могло обратиться к переменным в окружающем контексте. Например, если бы у Вас этот код, map() прервется, потому что функция лямбды работала бы с неопределенной ссылкой на переменную 'a'.

def spam(s):
    a = 4
    r = map(lambda x: a*x, s)

Существовали обходные решения этой проблемы, но они заключались в использовании «аргументов по умолчанию» и передачу скрытых параметров в лямбда-выражение. Например:

def spam(s):
    a = 4
    r = map(lambda x, a=a: a*x, s)


«Корректное» решение этой проблемы для внутренних функций было в том, чтобы неявно перенести ссылки на все локальные переменные в контекст функции. Данный подход известен как «замыкание» и является важным аспектом функциональных языков. Однако, эта возможность не была представлена в Python до выпуска версии 2.2.

Любопытно, что map, filter, и reduce функции, которые первоначально смотивировали введение лямбды, и других функциональных возможностей были в большой степени заменены на “list comprehensions” и генераторы. Фактически, функция reduce была удалена из списка встроенных функций в Python 3.0.

Даже при том, что я не задумывал Python как функциональный язык, введение замыканий было полезно в разработке многих других возможностей языка. Например, определенные аспекты классов нового стиля, декораторов, и других современных возможностей используют замыкания.

Наконец, даже при том, что много возможностей функционального программирования были введены за эти годы, Python все еще испытывает недостаток в определенном функционале из «настоящих» функциональных языков. Например, Python не выполняет определенные виды оптимизации (например, хвостовая рекурсия). Динамический характер Python сделал невозможным ввести оптимизации времени компиляции, известные в функциональных языкых как Haskell или ML.
Метки:
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 35
  • 0
    интересно. присматриваюсь к питону. хотя сам тоже пишу на императивных языках.
    • 0
      Python является императивным языком.
      • +2
        Скорее все-же мультипарадигменным.
        • +3
          Уж очень стало популярно использовать это слово — «мультипарадигменный».

          Императивный и функциональный стили — это не набор фич. Это филисофия решения задачи.
          • 0
            Вернее, не функциональный, а декларативный. Но из-за непопулярности логических языков, в данное время эти понятия практически слились.
            • –1
              Это Python-то декларативный? С чего бы это?
              • +2
                Питон — императивный язык.

                Выше, я написал про императивный и функциональный стили, хотя правильнее императивный и декларативный. Что и уточнил ниже.
            • +1
              Оно не зря стало популярно — многие современные языки являются гибридными, позволяя вполне себе решать определенный круг задач с использованием функционального подхода на императивных языках. Такая тенденция сейчас, философия здесь ИМХО ни причем.
              • –1
                согласен. но чтобы быть практически значимой, философия решения задачи должна поддерживаться фичами языка.

                то, что в python называется поддержкой ФП, в реальности является лишь синтаксическим сахаром, имитацией этой самой парадигмы. например, вот такой ФП-подобный код на «мультипарадигменном» python выглядит вполне органично:
                >>> f = lambda (x, y): x + y
                >>> a = (1, 2)
                >>> f(a)
                3
                

                но по сути, он чужеродный, ведь работает очень не эффективно. в процессе выполнения такой программы, в самом деле, создаются переменные, а лямбда распаковывает кортеж в пару локальных объектов. в «настоящих» функциональных языках картина совсем иная, т.к. там нет переменных.
          • +4
            Императивность ортогональна функциональности. Сколько, блин, можно путать?! Ещё раз:
            1) императивный <-> декларативный (речь идёт о задании явной последовательности действий либо о декларировании желаемого результата)
            2) процедурный <-> функциональный (результат достигается либо путём последовательных изменений состояния машины (машина Тьюринга), либо вычислением некоего выражения (лямбда-счисление). для последнего, например, не важен порядок вычисления аргументов функции)
            3) возможны любые сочетания — императивный функциональный, например.
            • –1
              и что же такое «императивный функциональный» — можно ссылки на терминологию?
              • –2
                > (let* ([a 1] [b (+ a 1)]) (+ a b))
                3
                

                например. императивно? да, тут явно задана последовательность действий. функционально? да, тут вычисляется значение выражения.
                • –1
                  g(f(x)) — проще говоря? =) тут явно задана последовательность действий: сначала f(), затем g(), и тут вычисляется значение выражения.
                  • –1
                    Во-первых, let* раскрывается вот так:
                    (let* ([a 1] 
                              [b (+a 1)]) 
                      (+ a b)) 

                    (let ([a 1]) 
                      (let ([b (+a 1)]) 
                         (+ a b)))

                    (let ([a 1]) 
                      (let ([b (+a 1)]) 
                         (+ a b)))

                    ((lambda (a) 
                       ((lambda (b) (+ a b)) 
                        (+ a 1))) 
                     1)
                    </source
                    Да, в данном случае это эквивалентно g(f(x)) (в конце концов, в лямбда-счислении всё есть вложенные функции), но в общем случае так говорить не совсем корректно.
                    Во-вторых, let* и есть императивное последовательное вычисление значений и биндинг их к переменным, сравните:
                    <source>
                    (let* ([a 1]
                           [b 2]
                           [c (a . + . b)]
                           [d (b . - . a)]) 
                      (c . + . d))
                    

                    int a = 1;
                    int b = 2;
                    int c = a + b;
                    int d = b - a;
                    return c + d;
                    

                    В третьих, я не совсем понял, с чем вы не согласны вообще — я сразу привёл этот код как пример императивного функционального стиля.
                    • –1
                      для начала я хочу разобраться в вашей терминологии. лишь после этого можно будет выражать согласие или не согласие. =)

                      последний пример (на си?) тоже императивный функциональный?
                      • –1
                        Нет, последнее скорее императивный процедурный. Потому как в примере кода на схеме 3 является значением выражения, а в коде на псевдо-Си мы явно задаём возврат откуда-то (с тем же успехом там мог быть какой-нибудь вывод на консоль, вся эта штука целиком не является выражением).
          • 0
            Товарищ автор статьи, скажите пожалуйста, а чем так привлекателен «функциональный стиль»? Я не силен в Python, но на Matlab есть очень даже элегантное решение вышеупомянутой проблеммы — операторы с точкой(".+" ".*" ....), которые позволяют применять некую операцию к каждому элементу массива(думаю, в Python тоже есть что-нибудь подобное)(обычно сих операторов много не нужно, иначе теряется структура и смысл записи, что заставляет задуматься в нужности подобного оформления). ИМХО люди, прогающие на функциональных языках сами усложняют себе жизнь, т.к. структурирование у последних просто скверное. В этом плане Python очень силен, т.к. он не станет работать, если код неструктурирован(если неправильно расставлены отступы по крайней мере), т.е. он приучавает человека к порядку, а не наоборот. Так как это личное субьективное мнение, критиковать не буду, но все же, задумайтесь на написанным.
            • +4
              Автор статьи — Гвидо ван Россум, создатель Python.

              Эта статья — экскурс в историю.
              • 0
                Тогда прошу простить. Не заметил.
              • +1
                > на Matlab есть очень даже элегантное решение вышеупомянутой проблеммы — операторы с точкой(".+" ".*" ....), которые позволяют применять некую операцию к каждому элементу массива
                Такое нужно только в numpy и оно там есть (перегрузкой обычных операторов). В языке общего назначения это не требуется, а требуется несколько другое.
                • +5
                  Гвидо обязательно задумается над написанным :-D
                  • 0
                    Тут проблемы, как принято говорить, не в языках, а в головах.
                    Если у человека проблемы с организацией — то они будут проявляться независимо от языка.
                    А структурирование делает любая человеческая IDE — раз уж речь о ФП, тот же SLIME по C-M-\ переформатирует блок, чтобы легко читалось…
                    • +1
                      > обычно сих операторов много не нужно
                      вот именно, вы мыслите операторами, а тут речь про целые функции. Простейший пример:

                      найдет максимальный элемент в списке элементов, причем сделает это по конкретному полю:
                      max(obj_list, key=lambda x: x.param)
                      

                      вернет список нечетных элементов содержащихся в списке lst:
                      filter(lambda x: x % 2, lst)
                      

                      Можно конечно написать это в цикле, но так красивее, короче а главное понятней.
                      • –1
                        Попытаюсь показать решение на своем любимом языке(на С++):
                        • –1
                          #define filter(list,filter,new_list) \
                          { \
                          new_list.clear(); \
                          for (typeof(list)::iterator it=list.begin(); it!=list.end(); ++it) \
                          if (filter(*it)) \
                          new_list.push_back(*it); \
                          }
                          вот приблизительно так.
                          • 0
                            На С++ это вот так

                            copy_if(list.begin(), list.end(), back_inserter(new_list), boost::lambda::....)

                            где лямбды вполне себе юзабельны.
                            А вы то на С накатали, не нужно позорить плюсы.
                            • –1
                              А вы мне copy_if распишите, и желаельно не задевайте буст, это отдельная тема с отдельной концепцией.
                              • –1
                                Мейерс достаточно написал, зачем плодить сущности? А буст это чистые плюсы, в чем проблема? В tr1 вошли некоторые вещи из буста, лямбды стали стандартом.
                        • 0
                          (x for x in lst if x & 1)
                        • +1
                          люди, прогающие на функциональных языках сами усложняют себе жизнь, т.к. структурирование у последних просто скверное. В этом плане Python очень силен, т.к. он не станет работать, если код неструктурирован
                          О блин, как структурированность с функциональной парадигмой связана вообще?
                        • +7
                          Приятно почитать про любимый язык.
                          • 0
                            Спасибо! Узнал немного нового
                            • +2
                              Добавлю python-history.blogspot.com/ в RSS-reader, спасибо!
                              • 0
                                Вот только тишина там аж с августа. Гвидо, пиши ещё!
                              • 0
                                Спасибо за перевод и ссылку на интересный блог.

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.