Ловушки/Недостатки функционального программирования

Когда вы не хотите использовать функциональное программирование? Что это не так хорошо?

Я больше ищу недостатки парадигмы в целом, а не такие вещи, как "не широко используется" или "нет хорошего отладчика". Эти ответы могут быть правильными на данный момент, но они касаются того, что FP является новой концепцией (неизбежной проблемой), а не присущими ей качествами.

по теме:

Ответ 1

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

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

  • Вероятно, что функциональная программа, написанная новичком, будет излишне медленной и более вероятной, чем, скажем, программа C, написанная новичком C. С другой стороны, примерно одинаково вероятно, что Программа С++, написанная новичком, будет излишне медленной. (Все эти блестящие функции...)

    Как правило, специалистам не трудно писать быстрые функциональные программы; и на самом деле некоторые из наиболее эффективных параллельных программ для 8- и 16-ядерных процессоров теперь написаны в Haskell.

  • Скорее всего, кто-то, начиная функциональное программирование, откажется от реализации обещанного прироста производительности, чем кто-то начнет, скажем, Python или Visual Basic. Там просто не так много поддержки в виде книг и средств разработки.

  • С кем меньше людей разговаривают. Stackoverflow - хороший пример; относительно немногие программисты Haskell регулярно посещают сайт (хотя часть этого состоит в том, что программисты Haskell имеют свои собственные оживленные форумы, которые намного старше и лучше установлены, чем Stackoverflow).

    Также верно, что вы не можете легко разговаривать со своим соседом, потому что концепции функционального программирования сложнее обучать и усерднее, чем объектно-ориентированные концепции языков, таких как Smalltalk, Ruby и С++. Кроме того, объектно-ориентированное сообщество потратило годы на разработку хороших объяснений того, что они делают, тогда как сообщество функционального программирования, похоже, считает, что их материал, очевидно, велик и не требует каких-либо специальных метафор или лексики для объяснения. (Они ошибаются. Я все еще жду первую книгу "Функциональные шаблоны проектирования".)

  • Известный недостаток ленивого функционального программирования (применим к Haskell или Clean, но не к ML или Scheme или Clojure) - это то, что очень сложно предсказать затраты времени и пространства для оценки ленивая функциональная программа, даже эксперты не могут этого сделать. Эта проблема имеет основополагающее значение для парадигмы и не уходит. Есть отличные инструменты для обнаружения поведения времени и пространства post facto, но чтобы эффективно использовать их, вы уже должны быть экспертами.

Ответ 2

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

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

(Спасибо Джареду Апдайку за предложение списка разниц.)

Ответ 3

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

На самом деле вам не нужно смотреть дальше этого потока, чтобы увидеть его:

"Обычно специалистам не сложно писать быстрые функциональные программы, и на самом деле некоторые из наиболее эффективных параллельных программ на 8- и 16-ядерных процессорах теперь написаны в Haskell".

Подобные заявления могут дать вам впечатление, что эксперты выбирают Haskell, потому что это может быть так хорошо для parallelism, но правда в том, что производительность Haskell отстойна, а миф о том, что Haskell хорош для многоядерных parallelism, увековечен исследователями Haskell с небольшими знаниями о parallelism, которые избегают реальной рецензии, публикуя только в зоне комфорта журналов и конференций под контролем своей собственной клики. Haskell невидим в реальном параллельном/многоядерном/HPC в реальном мире именно потому, что он сосет при параллельном программировании.

В частности, настоящая проблема в многоядерном программировании использует преимущества кэшей CPU, чтобы убедиться, что ядра не голодают из данных, проблема, которая никогда не рассматривалась в контексте Haskell. Группа Charles Leiserson в MIT отлично справилась с этой проблемой и решила эту проблему, используя собственный язык Cilk, который стал основой параллельного программирования в реальном мире для мультикодов как в Intel TBB, так и в Microsoft TPL в .NET 4. Существует превосходное описание того, как этот метод можно использовать для написания элегантного высокоуровневого императивного кода, который компилируется для масштабируемого высокопроизводительного кода в бумаге 2008 Сложность кэша многопоточных алгоритмов кэширования. Я объяснил это в моем обзоре в одном из самых современных исследований Parallel Haskell.

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

EDIT: Техасские многоядерные технологии также недавно обнаружили, что Haskell не впечатляет в контексте многоядерных parallelism.

Ответ 4

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

Функциональные идиомы часто делают много инверсии контроля или лень, что часто отрицательно сказывается на отладке (с использованием отладчика). (Это несколько компенсируется тем, что FP намного меньше подвержена ошибкам из-за неизменяемости/ссылочной прозрачности, что означает, что вам нужно будет отлаживать реже.)

Ответ 5

Филипп Вадлер написал статью об этом (так называемый "Почему никто не использует языки функционального программирования" ) и обратился к практическим ловушкам, препятствующим людям использовать языки FP:

Обновление: недоступная старая ссылка для пользователей с доступом ACM:

Ответ 6

Помимо проблем с быстротой или принятием и решения более фундаментальной проблемы, я слышал, что с функциональным программированием очень легко добавлять новые функции для существующих типов данных, но "сложно" добавлять новые типы данных. Рассмотрим:

(Написано в SMLnj. Также, пожалуйста, извините несколько надуманный пример.)

datatype Animal = Dog | Cat;

fun happyNoise(Dog) = "pant pant"
  | happyNoise(Cat) = "purrrr";

fun excitedNoise(Dog) = "bark!"
  | excitedNoise(Cat) = "meow!";

Я могу очень быстро добавить следующее:

fun angryNoise(Dog) = "grrrrrr"
  | angryNoise(Cat) = "hisssss";

Однако, если я добавлю новый тип в Animal, я должен пройти через каждую функцию, чтобы добавить поддержку для него:

datatype Animal = Dog | Cat | Chicken;

fun happyNoise(Dog) = "pant pant"
  | happyNoise(Cat) = "purrrr"
  | happyNoise(Chicken) = "cluck cluck";

fun excitedNoise(Dog) = "bark!"
  | excitedNoise(Cat) = "meow!"
  | excitedNoise(Chicken) = "cock-a-doodle-doo!";

fun angryNoise(Dog) = "grrrrrr"
  | angryNoise(Cat) = "hisssss"
  | angryNoise(Chicken) = "squaaaawk!";

Обратите внимание, что для объектно-ориентированных языков истинная противоположность верна. Очень легко добавить новый подкласс в абстрактный класс, но может потребоваться утомительно, если вы хотите добавить новый абстрактный метод в абстрактный класс/интерфейс для всех подклассов, которые нужно реализовать.

Ответ 7

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

Я изучаю класс функций fold теперь уже три дня. У Fold, похоже, очень простое приложение: взять список и уменьшить его до одного значения. Для этого Haskell использует foldl и foldr. Эти две функции имеют множество разных реализаций. Существует альтернативная реализация foldl, называемая foldl'. Кроме того, существует версия с немного отличающимся синтаксисом под названием foldr1 и foldl1 с разными начальными значениями. Из них соответствует реализация foldl1' для foldl1. Как будто все это не было безумием, функции, которые fold[lr].* требуют в качестве аргументов и используют внутри редукции, имеют две отдельные подписи, только один вариант работает в бесконечных списках (r), и только один из них выполняется в постоянная память (как я понимаю (L), потому что ей требуется только redex). Понимание того, почему foldr может работать в бесконечных списках, требует по крайней мере достойного понимания языков lazy-behavoir и мелких деталей, что не все функции будут вынуждать оценку второго аргумента. Графики онлайн для этих функций запутывают как ад для тех, кто никогда не видел их в колледже. Нет эквивалента perldoc. Я не могу найти ни одного описания того, что делает любая из функций в прелюдии Haskell. Прелюдия - это своего рода предустановленный дистрибутив, который поставляется с ядром. Мой лучший ресурс - это действительно тот парень, с которым я никогда не встречался (Кейл), который помогает мне за огромный счет в свое время.

О, и фолд не должен сводить список к скалярному типу не-списка, функция идентификации для списков может быть записана foldr (:) [] [1,2,3,4] (основные моменты, которые вы можете скопировать в список).

/me возвращается к чтению.

Ответ 8

Вот некоторые проблемы, с которыми я столкнулся:

  • Большинство людей считают, что функциональное программирование трудно понять. Это означает, что вам, вероятно, будет труднее написать функциональный код, и ему наверняка будет сложнее забрать его.
  • Функциональные языки программирования обычно медленнее, чем язык c. С течением времени это становится менее проблемой (потому что компьютеры становятся быстрее, а компиляторы становятся умнее).
  • Не так широко распространенные, как их настоящие коллеги, может быть трудно найти библиотеки и примеры для общих проблем программирования. (Например, его почти всегда легче найти что-то для Python, то это для Haskell)
  • Отсутствуют инструменты, особенно для отладки. Это определенно не так просто, как открыть Visual Studio для С# или eclipse для Java.

Ответ 9

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

  • Кажется сравнительно редким, что практично выбрать функциональную модель некоторой реальной проблемы над императивной. Когда проблемная область является обязательной, использование языка с этим признаком является естественным и разумным выбором (поскольку в целом рекомендуется минимизировать расстояние между спецификацией и реализацией как часть уменьшения количества тонких ошибок). Да, это может быть преодолено достаточно умным кодером, но если вам нужна Rock Star Coders для этой задачи, это потому, что это слишком тяжело.

  • По какой-то причине я никогда не понимал, что языки функционального программирования (или, возможно, их реализации или сообщества?) гораздо чаще хотят иметь все на своем языке. Там гораздо меньше использования библиотек, написанных на других языках. Если у кого-то есть особенно хорошая реализация некоторой сложной операции, это имеет смысл использовать это вместо того, чтобы создавать свои собственные. Я подозреваю, что это отчасти связано с использованием сложных временных рядов, которые затрудняют обработку внешнего кода (и особенно его эффективное). Я бы хотел, чтобы в этом вопросе было не так.

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