Если мы можем делать функциональное программирование на Python, нужен ли нам конкретный язык функционального программирования? [закрыто]

22

Используя генераторы и лямбду, мы можем выполнять функциональное программирование на Python. Вы также можете достичь того же с Ruby.

Поэтому возникает вопрос: зачем нам нужны специальные функциональные языки программирования, такие как Erlang, Haskell и Scheme? Есть ли что-то другое, что обеспечивают эти специфические функциональные языки программирования? Почему мы не можем просто использовать Python для функционального программирования?

Джошуа Партоги
источник
30
все это было еще до того, как был создан питон
Махмуд Хоссам
51
Форд Пинто это автомобиль. Зачем нам нужны конкретные быстрые машины, такие как Ferrari?
Мартин Йорк,
11
Используя классы и шаблоны, мы можем делать все что угодно в C ++. Почему Java и Python были когда-либо созданы? Что они добавляют?
9000
19
Все языки программирования (за исключением некоторых чисто академических языков исследований) эквивалентны по Тьюрингу, поэтому то, что вы можете делать на языке А, вы можете делать на любом другом языке. Итак, следуя этой мысли, нам нужен только один полный язык Тьюринга - скажем, как sendmail.cf;) okmij.org/ftp/Computation/#sendmail-Turing
Maglob
17
Если бы вы знали какой-либо из этих языков, вы бы не заявили, что Python хорошо выполняет функциональное программирование. Это не так. Он делает это достаточно хорошо, чтобы включить часть вещей FP-иша, но не лучше.

Ответы:

20

Я ценю этот вопрос, потому что лично я большой поклонник как Python, так и функционального стиля программирования. У меня большой опыт работы с Python, и я недавно начал изучать Haskell, поэтому вот некоторые моменты, основанные на моем личном опыте, касающемся различий между этими языками, с функциональной точки зрения.

чистота

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

Спектакль

Вы можете или не можете заботиться о производительности, в зависимости от того, что является областью вашего приложения, но статическая типизация и гарантированная чистота дают компилятору гораздо больше возможностей для работы, по сравнению с Python и другими динамическими языками (хотя я должен признать, что PyPy делает великолепно и, например, LuaJIT граничит с чудесным).

Оптимизация Tail-Call

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

Синтаксис

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

  • Синтаксис лямбда-функций довольно многословен и ограничен в том, что они могут содержать
  • Нет синтаксического сахара для функционального состава, т.е. f = g . hпротивf = lambda *arg: g(h(*arg))
  • Нет синтаксического сахара для частичного применения, т.е. f = map gпротивf = functools.partial(map, g)
  • Нет синтаксический сахара для использования операторов инфиксных в функциях высшего порядка т.е. sum = reduce (+) lstпротивsum = reduce(operator.add, lst)
  • Отсутствует сопоставление с образцом или защита аргументов функции, которые позволяют легко выразить условия окончания рекурсии и некоторые граничные случаи с очень читаемым синтаксисом.
  • Скобки никогда не являются необязательными для вызовов функций, и нет синтаксического сахара для вложенных вызовов. Я предполагаю, что это дело вкуса, но особенно в функциональном коде, я нахожу, что это обычное явление для цепочки вызовов функций, и мне y = func1 $ func2 $ func3 xлегче читать, чем y = func1(func2(func3(x)))когда вы знакомы с этой нотацией.
шан
источник
28

Это самые важные различия:

Haskell

  • Ленивая оценка
  • Компилируется в машинный код
  • Статическая типизация обеспечивает чистоту функций
  • Тип вывода

Хаскелл и Эрланг

  • Сопоставление с образцом

Erlang

  • Актерская модель параллелизма, легковесные процессы

Схема

  • макрос

Все языки

  • реальные замыкания (у ruby ​​есть замыкания, можно ли обсуждать Python, см. комментарии)
  • стандартная библиотека, подходящая для функционального стиля программирования (неизменяемые коллекции, карта, фильтр, сгиб и т. д.)
  • хвостовая рекурсия (это можно найти и в некоторых нефункциональных языках)

Кроме того, вам следует взглянуть на языки из семейства ML, такие как SML, Ocaml и F # и Scala, которые по-новому объединяют ОО и функциональное программирование. Все эти языки имеют уникальные интересные особенности.

Ким
источник
3
+1 хороший пост. Вы можете добавить вывод типов в Haskell и облегченные процессы в Erlang.
Джонас
1
Python имеет карту, фильтр и сложить (уменьшить). Относительно «реальных замыканий»: если вы определяете действительное замыкание как замыкание, которое может содержать что-то отличное от одного выражения, у Haskell также нет реальных замыканий (но, конечно, в Haskell есть несколько вещей, которые не являются выражениями ...) , Тем не менее, вопрос о неизменных структурах данных является хорошим, поэтому +1 за это. Также: хвостовая рекурсия (и вообще менее дорогая рекурсия).
sepp2k
2
+1 за "статическая типизация гарантирует чистоту функций". Очень круто иметь систему типов, которая различает чистые и не чистые функции. (C ++ const doe snot count. :)
Macke
1
@btilly: я бы не стал считать это закрытием, если вы не можете присвоить переменную, которая находится в области видимости. В противном случае мы должны были бы сказать, что в Java тоже есть замыкания, поскольку вы можете использовать тот же прием.
Ким
3
В закрытии я могу получить доступ к переменной так же, как обычно. Это верно для замыканий Хаскелла и Руби, но не для плохих заменителей Python или Java. Может быть, кто-то еще может рассказать нам об Эрланге, я не очень хорошо это знаю.
Ким
19

Трудно точно определить, что такое «функциональный язык» - из перечисленных вами языков только Haskell является чисто функциональным (все остальные используют какой-то гибридный подход). Тем не менее, существуют определенные языковые функции, которые очень полезны для функционального программирования, и у Ruby и Python их недостаточно, чтобы быть очень хорошей средой для FP. Вот мой личный контрольный список, в порядке важности:

  1. Первоклассные функции и замыкания (Ruby, Python и все остальные, которые вы перечислили, имеют это).
  2. Гарантированная оптимизация хвостового вызова (Erlang, Haskell, Scala и Scheme имеют это, но не Python, Ruby или Clojure (пока)).
  3. Поддержка неизменяемости в языке и стандартных библиотеках (это большая поддержка, которую имеют все «функциональные языки», которые вы перечислили (кроме Scheme), но Ruby и Python не имеют).
  4. Поддержка на уровне языка для ссылочно прозрачных (или чистых) функций (насколько я знаю, только Haskell имеет это в настоящее время).

Необходимость (1) должна быть очевидна - функции высшего порядка чрезвычайно сложны без функций первого класса. Когда люди говорят, что Ruby и Python являются хорошими языками для FP, они обычно говорят об этом. Тем не менее, эта особенность необходима, но не достаточна, чтобы сделать язык хорошим для FP.

(2) была традиционная необходимость в FP с тех пор, как была изобретена схема. Без TCO невозможно программировать с глубокой рекурсией, которая является одним из краеугольных камней FP, потому что вы получаете переполнение стека. Единственный «функциональный» (по общему определению) язык, который не имеет этого, - это Clojure (из-за ограничений JVM), но у Clojure есть множество хаков для имитации TCO. (К сведению, Ruby TCO зависит от реализации , но Python специально не поддерживает его .) Причина, по которой TCO должен быть гарантирован, заключается в том, что если это зависит от реализации, глубокие рекурсивные функции будут порваться с некоторыми реализациями, поэтому вы не сможете по-настоящему использовать их на всех.

(3) еще одна важная вещь, которую современные функциональные языки (особенно Haskell, Erlang, Clojure и Scala) имеют, чего нет в Ruby и Python. Не вдаваясь в подробности, гарантированная неизменность устраняет целые классы ошибок, особенно в параллельных ситуациях, и допускает такие аккуратные вещи, как постоянные структуры данных . Этими преимуществами очень сложно пользоваться без поддержки на уровне языка.

(4) для меня самая интересная вещь в чисто функциональных языках (в отличие от гибридных). Рассмотрим следующую чрезвычайно простую функцию Ruby:

def add(a, b)
  a + b
end

Это похоже на чистую функцию, но из-за перегрузки оператора она может изменить либо параметр, либо вызвать побочные эффекты, такие как печать на консоль. Маловероятно, что кто-то перегружает +оператора, чтобы иметь побочный эффект, но язык не дает никаких гарантий. (То же самое относится и к Python, хотя, возможно, не с этим конкретным примером.)

В чисто функциональном языке, с другой стороны, существуют гарантии уровня языка, что функции прозрачны по ссылкам. Это имеет многочисленные преимущества: чистые функции легко запоминаются; их можно легко протестировать, не полагаясь ни на какое глобальное состояние; и значения внутри функции могут оцениваться лениво или параллельно, не беспокоясь о проблемах параллелизма. Haskell в полной мере использует это, но я не знаю достаточно о других функциональных языках, чтобы знать, делают ли они.

При этом можно использовать технику FP практически на любом языке (даже на Java). Например, Google MapReduce вдохновлен функциональными идеями, но, насколько я знаю, они не используют никаких «функциональных» языков для своих больших проектов (я думаю, что они в основном используют C ++, Java и Python).

shosti
источник
2
+1 за подробное объяснение - даже я, как сторонний наблюдатель, понял это. Благодарность! :-)
Петер Тёрёк
самый ценный ответ на этот вопрос до сих пор. Основанный на фактах и ​​большом количестве информации. Отличная работа.
wirrbel
У Scala есть хвостовая рекурсия, и, как и в Scheme, она выполняется автоматически, если рекурсивный вызов находится в хвостовой позиции (в отличие от Clojure, где он должен быть явно запрошен). Есть даже аннотация, так что вы можете проверить, что компилятор будет генерировать хвостовой рекурсивный код. Чего нет, так это более обобщенной совокупной стоимости владения Схемы. Я предполагаю, что вы это знаете, но поскольку вы вдавались в подробности большинства других вещей, это казалось странным увольнением / упущением.
Брюс
@itsbruce Это сообщение довольно старое, в то время его не было в IIRC Scala (или, возможно, я просто ошибался;). Обновлено.
Шости
Я не использовал Scala с самого начала, но у него была хвостовая рекурсия в 2008 году, когда я заинтересовался ;-) Я отсылаю людей, которые спрашивают об этой теме, к этому конкретному вопросу SO, потому что у него есть несколько хороших ответов, и я только заметил эту странность, поэтому прокомментировал полноту.
Брюс
11

Языки, которые вы упоминаете, очень разные.

В то время как Python и Ruby являются динамически типизированными языками, Haskell статически типизирован. Erlang - это параллельный язык, использующий модель Actor, и он сильно отличается от всех других языков, которые вы упоминаете.

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

Jonas
источник
@kRON: Ну, система типов является важным свойством языка, и он спросил: «Есть ли что-то отличное, что обеспечивают эти конкретные языки функционального программирования?». Конечно, вы можете использовать модель Actor с другими языками, но в Erlang она встроена в язык. Erlang использует легковесные процессы и имеет встроенные языковые конструкции для распределенного программирования - отсюда «параллельный» язык.
Джонас
8

Опоздал на вечеринку, как обычно, но все равно собирался что-то сказать.

Функциональный язык программирования не является языком, который допускает функциональное программирование. Если бы мы следовали этому определению, то практически любой язык в любом месте является функциональным языком программирования. (Кстати, то же самое относится и к ООП. Вы можете писать в стиле ООП на С, если хотите. Таким образом, согласно вашей логике, С является языком ООП.)

Что делает функциональный язык программирования не тем, что позволяет вам программировать, а тем, что позволяет вам легко программировать . Это ключ.

Итак, в Python есть лямбды (которые являются невероятно анемичными, похожими на замыкания) и предоставляет вам пару библиотечных функций, которые вы увидите в функциональных библиотеках, таких как «map» и «fold». Однако этого недостаточно для того, чтобы сделать его функциональным языком программирования, потому что трудно или невозможно последовательно программировать в надлежащем функциональном стиле (и язык, безусловно , не применяет этот стиль!). По своей сути Python является императивным языком, связанным с операциями манипулирования состоянием и состоянием, и это просто противоречит семантике выражения и оценки выражения функционального языка.

Так почему же у нас есть функциональные языки программирования, когда Python (или Ruby (или вставьте язык по вашему выбору)) может «заниматься функциональным программированием»? Потому что Python и др. Не могут делать правильное функциональное программирование. Поэтому.

ПРОСТО МОЕ правильное мнение
источник
6

Вы можете заниматься функциональным программированием на Java (см., Например, http://functionaljava.org/ ). Вы также можете заниматься объектно-ориентированным программированием на языке Си . Это просто не так идиоматично.

Так что на самом деле нам совершенно не нужны Erlang, Haskell, Scheme или какой-либо конкретный язык программирования, но все они представляют разные подходы и различные компромиссы, что делает некоторые задачи проще, а некоторые сложнее. Что вы должны использовать, зависит от того, чего вы хотите достичь.

Joonas Pulakka
источник
4

Этот вопрос может быть применен к бесконечному количеству языков и парадигм.

  • Поскольку все используют C ++, зачем нам нужны другие языки общего назначения?
  • Так как java - такой замечательный ОО-язык, почему существуют другие ОО-языки?
  • Поскольку Perl - удивительный язык сценариев, зачем нам Python?
  • Ятта, ятта, ятта

Большинство, если не все языки существуют по определенной причине. Они существуют, потому что у кого-то была потребность в том, чтобы ни один текущий язык не заполнялся или заполнялся плохо. (Это, конечно, относится не ко всем языкам, но я чувствую, что это относится к большинству известных языков.) Например, python изначально был разработан для взаимодействия с ОС Amoeba [ 1 , 2 ], а Erlang был создан, чтобы помочь в разработке приложений телефонии [ 3 ]. Итак, один ответ на вопрос «Зачем нам нужен другой функциональный язык?» может быть просто потому, что [insert-name-of-the-кто-то-кто-знает-как-к-дизайн-языкам] не понравилось, как это сделал python.

Это в значительной степени подводит итог того, что я думаю, ответ. Хотя вы можете делать с Python все, что вы можете делать с функциональным языком, вы бы действительно этого хотели? Все, что вы можете делать в C, вы можете делать в сборке, но хотите ли вы? Разные языки всегда будут лучше всего делать разные вещи, и так и должно быть.

cledoux
источник
2

Функциональное программирование - это не только парадигма дизайна, но и специфические особенности языка. Или, другими словами, лямбды и функция карты не являются функциональным языком программирования. Python и Ruby имеют некоторые функции, вдохновленные функциональными языками программирования, но вы все равно обычно пишете код очень настоятельно. (Это похоже на C: вы можете написать OO-подобный код на C, но никто серьезно не считает C языком OO.)

Посмотрите: функциональное программирование - это не только лямбда map-функции или функции высшего порядка. Это о дизайне . Программа, написанная на «истинном» языке функционального программирования, решает проблемы посредством композиции функций. Хотя программы, написанные на Ruby или Python, могут использовать функции, подобные FP, они обычно не читаются как набор составных функций.

mipadi
источник
0

Каждый упомянутый вами функциональный язык вполне вписывается в определенную нишу, и идиоматические шаблоны, которые каждый поощряет, делают их очень хорошо подходящими для определенных задач, которые было бы невозможно выполнить в Python, если бы вы не создали огромную библиотеку вспомогательных модулей. Самым очевидным из примеров такого превосходства является модель параллелизма Эрланга. У других также есть подобные сильные стороны.

davidk01
источник
0

Каждая концепция, доступная в лямбда-исчислении, LISP и Схеме, доступна в Python, так что, да, вы можете выполнять в ней функциональное программирование. Если это удобно или нет, это вопрос контекста и вкуса.

Вы можете легко написать интерпретатор LISP (или другого функционального языка) на Python (Ruby, Scala) (что это значит?). Вы можете написать интерпретатор для Python, используя чисто функциональный, но это займет много работы. Даже «функциональные» языки в настоящее время являются мультипарадигмой.

Эта старая и красивая книга содержит большинство (хотя и не все) ответов о сущности функционального программирования.

апалала
источник
@Arkaaito Но согласны ли вы с тем, что каждая концепция, доступная в лямбда-исчислении, LISP и схеме , доступна в Python?
Марк С
Вы уверены, что каждая концепция LISP доступна в Python? Макросы, код-это-данные?
SK-logic
@ Макросы SK-логики не входят в лямбда-исчисление, но да, они доступны в Python, хотя и не под этим именем. Python предоставляет eval()функцию, которая необходима для того, чтобы код представлял собой данные , но он идет дальше: он позволяет вам изменять большую часть среды выполнения, как в LISP.
Апалала
@Apalala, динамические языки eval()- это метапрограммирование во время выполнения. Иногда это полезно, но это чрезвычайно дорого. Макросы Lisp разные, это метапрограммирование времени компиляции. Он не доступен в Python в любой доступной форме.
SK-logic
@ SK-logic Такие макросы нарушают предпосылку « код-данные» , так как программы не могут изменять макросы (или даже знать об их существовании). В Python программы имеют доступ к своим собственным деревьям разбора во время выполнения, если они в них нуждаются. Макросы (статическая предварительная обработка) вообще не работают.
Апалала
-5

Потому что Python также может программировать в нефункциональном стиле, и этого недостаточно для FP-пуриста. Более прагматичные программисты, тем не менее, могут пользоваться преимуществами функционального стиля, не будучи догматичными по этому поводу:

«Функциональный программист звучит скорее как средневековый монах, отказывая себе в удовольствиях жизни в надежде, что это сделает его добродетельным. Для тех, кто больше заинтересован в материальных выгодах, эти «преимущества» не очень убедительны. Функциональные программисты утверждают, что есть большие материальные преимущества… [но] это просто смешно. Если бы пропущенные операторы присваивания приносили такие огромные преимущества, программисты FORTRAN делали бы это в течение двадцати лет. Логически невозможно сделать язык более мощным, опуская функции, какими бы плохими они ни были ».

- Джон Хьюз, Почему функциональное программирование имеет значение

Мейсон Уилер
источник
6
Я согласен. Любой язык без gotoужасен.
Анон.
2
@ Anon: Или его большой брат call-with-current-continuation.
Крис Джестер-Янг
4
Мейсон, газета, которую ты цитируешь, говорит прямо противоположное. Ваша цитата - это всего лишь пара абзацев статьи, в которой рассказывается другая история. На самом деле, если бы вы процитировали оба абзаца в целом, они бы явно показывали другое значение.
Марко Мустапик
2
@ Мейсон: да, автор утверждает, что модульность и ленивая оценка являются истинными преимуществами. Но обратите внимание, что ваша оригинальная цитата была вне контекста - вы, кажется, предполагаете, что автор заявляет что-то против FP, когда на самом деле вы цитировали абзац из статьи с выводом, что FP - это здорово, но не по причине вводящей в заблуждение причины " меньше - больше". Название статьи довольно ясно показывает намерения автора: «почему функциональное программирование имеет значение» ;-) Это, безусловно, не поддерживает ваше утверждение о том, что более чистые языки FP «являются неприятностью».
Андрес Ф.
6
@ Мейсон Уилер: Гибридные языки предшествуют Python намного длиннее. Например, Lisp датируется 1959 годом и является языком с множеством парадигм. Он полностью поддерживает функциональные подходы, процедурные подходы и объектно-ориентированные подходы к программированию. С правильными макропакетами вы также можете довольно легко выполнять логическое программирование. Схема тоже предшествует Python (и этой статье). Это восходит к 1975 году. Может быть, вам стоит взглянуть на эту временную шкалу языков программирования когда-нибудь.
Просто мое правильное мнение