Функциональное программирование не работает (и что с этим делать)

Перевод статьи Functional Programming Doesn't Work (and what to do about it) | Автор: James Hague (спасибо!)

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

Имея дело с функциональным программированием, и используя Erlang для решения сложных проблем, я, наконец, пришел к выводу, что чисто функциональное программирование не стоит того. Это не провал из-за простых вопросов, таких как маркетинг, а потому, что дальше вы идете вниз чисто функциональную дорогу более психического головой участвует в написании сложных программ. Это звучит как описание программирования в целом - проблемы становятся гораздо сложнее по мере увеличения в объеме. Часто огромная головоломка в Erlang (или Haskell) превращается в простой код в Python, или Perl, или даже С

Представьте, что вы реализовали крупную программу в чисто функциональном стиле. Все данные правильно собраны в функции, нет действительно разрушительные обновлений. Теперь выберите два низших уровня и наиболее изолированные функции во всей кодовой базе. Они используются повсюду, но никогда не вызываются из тех же модулей. Теперь позвольте им зависеть друг от друга: функция А ведет себя по-разному в зависимости от количества запусков функции B и наоборот.

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

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

if (a > 0) {
   a++;
}

Вводится новая переменная, чтобы избежать изменения существующей переменной, а результат ближе к Erlangy:

if (a > 0) {
   a1 = a + 1;
} else {
   a1 = a;
}

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

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

В этот момент я должен пояснить: функциональное программирование полезно и важно. Помните, оно было разработано как способ упростить рассуждения, а также чтобы избежать "обновления спагетти памяти". Граница между "императивом" и "функционалом" размыта. Если программа на Haskell содержит BASIC-подобный домен конкретного языка, который также написанный на Haskell, это общая программа функциональной или необходимость? Имеет ли это значение?

В некоторых случаях это известно и принято – генерация случайных чисел (где сиды модифицированы за кадром), и различных I / O (где позиция в файле управляется под вас).

Чтобы научиться находить похожие моменты в вашем собственном коде, требуется практика.

Один совет, который я могу предложить, что происходит за очевидным решения структур данных ядра из функционального к императивному кода, не может быть лучшим подходом. В примере с Pac-Man из "чисто функциональный ретро-игр", это совершенно выполнимо, можно написать эту старую игру-аркаду в чисто функциональном стиле. Зависимости могут быть разработаны; поток данных, на самом деле, – не так уж и плохо. Он по-прежнему может быть грязным, с большим количеством маленьких участков данных для отслеживания. Теперь очевидно, цель – состояние самого Pac-Man или призраков, но это часть потока данных основной программы. Сделайте их глобально доступными и изменяемыми, и вдруг большая часть кода перешла от императива к функционалу... что не было целью.

Лучше смотреть на маленькие участки данных, используемые в разных местах, а не только главный поток данных. Хороший кандидат в этом примере – текущее время игры (ака количество прошедших кадров). Понятен прецедент, функции времени / даты, вроде Erlang now(). Другой возможностью является оценка. Это простое значение, которое обновляется в различных ситуациях. Что делает его истинным глобальный счетчик, который удаляет весь слой данных, и это просто: функции добавляют к счету счетчика другую функцию для получения текущего значения. Нет причинл для усложнения, имея единую глобальную переменную – то, на что кодеры С / Python / Lua / Рубин даже не замечают.