Прочитал вот статью и содержимое обсуждения, но так и не нашёл ответов на мучающие меня вопросы. Возможно, кто-нибудь сможет откомментировать мои мысли?
1. Однако. Теоретически всё хорошо, на маленьких примерах всё отлично, но при обращении к большим примерам (а на Haskell их немало), то я вижу такие вещи.
1.1. Конечно, возможно, это отлично, что каждый может определить свои примитивы для control flow. Вроде монад, скобок, стрелки. Но оказывается (?) этих примитивов не хватает. Они не универсальны, и в большом проекте возникает куча собственных промежуточных абстракций, которые реализуют собственный control flow через манипулирование функциями. Можно сказать, что это отлично, что так и должно быть, ведь, это — true fp.
Но такой подход приводит к куче мелких функций, состоящих сплошь из идентификаторов, смысл которых совершенно непонятен. И непонятно, от куда эти функции взялись, и где они будут использоваться. Для того, чтобы вникнуть в происходящее, нужно пройти весь граф вычислений от вершины до самого низа, где и запрятана вся функциональность. Непонятно, как можно менять эту структуру, добавлять функциональность в ней, оптимизировать, улучшать, не меняя эту структуру целиком, что сложно.
Хороший пример этого неудобства — книга Пейтона Джонса о разработке компилятора и виртуальной машины для функционального языка. Где для каждой мелкого с точки зрения функциональности дополнения приходится переписывать ВСЕ функции. Конечно, эти изменения внести можно быстро благодаря структуре языка. Но их нужно внести МНОГО.
И это черта не только примеров в этой книге. Я попробовал немножко похакать вокруг исходников Frag — приходится делать то же самое. Что я делаю не так?
1.1.1. Возможно, это поведение связано с тем, что конструируется main — 'высокоматематичный' объект. Функция. А, как известно, математические объекты никакой динамикой не обладают. И чтобы эти объекты менять, их нужно менять целиком, на всех уровнях.
1.2. Сначала в Haskell мы упорно уходим от side effects, уходим от переменных, а потом очень много кода пишем при помощи монад. Но монады — это не очень гибкий инструмент, потому что связывание функций возможно только единственным способом. Конечно, можно иметь несколько bind'ов, но это вряд ли приведёт к улучшению понимания кода. Да и как может быть заранее известно, какой именно bind понадобится, и как они должны взаимодействовать. Поэтому, для организации dataflow:
1.2.1. Нужно делать world-состояние большим, включая в него большое количество значений. Но это сводит на нет все утверждения об автоматическом распараллеливание. Вычисление в монаде строго последовательные. А монады сплошь и рядом. Непонятно в этом случае, а зачем уходить от переменных и от описания dataflow при помощи них? Да, они требуют более мощных алгоритмов автоматического распараллеливания, но эти алгоритмы дают и больше возможностей. А насчёт того, насколько это возможно, так посмотрите компиляторы Fortran и С от Intel и Sun.
1.2.2. Можно попробовать разбить все вычисления на вычисления в небольших монадках, но это увеличивает количество аргументов у функций, заставляет придумывать функции для склейки значений. Снова усложнение. Зачем, если с переменными проще?
1.2.3. Стрелка спасает, но не очень эффективно: опять же требует много дополнительного кода для манипуляции над значениями.
2. Возможно, всё хорошо, пока дело не доходит до вызова main. Вызов — же это действие — 'примени main к такому-то значению', а не описание применения. Что-то у меня в голове не сходится: мы долго бежим от того, чтобы использовать действия, и в итоге, оно оказывается чуть ли ни самой фажной частью системы. Зачем писать программы, которые никто не запускает? Как насчёт концептуальной целостности?
В чём я не прав? Надеюсь, тут есть гуру, которые смогут открыть мне глаза.