Функциональное программирование на языке Haskell/Работа над ошибками/Фактологические ошибки

Материал из свободной русской энциклопедии «Традиция»
Перейти к навигации Перейти к поиску

« Функциональное программирование на языке Haskell / Работа над ошибками

Реестр фактологических ошибок[править | править код]

Лисп[править | править код]

Прежде всего следует сказать, что не стоит рассматривать Лисп[1] исключительно как язык функционального программирования. С 60-х годов прошлого века многое изменилось, и первый почти, как верно отметил автор, функциональный язык — не исключение: все это время он развивался, причем функциональное программирование не было основным направлением его развития. В итоге Лисп прочно занял уникальную экологическую нишу мультипарадигменного языка метапрограммирования (что, естественно, не отменяет его функциональных особенностей; более того, по очевидным причинам считается правильным писать то, что можно, в функциональном стиле) — и именно в этом качестве его и следует рассматривать.

Итак, приступим.

Синтаксис[править | править код]

  • Стр. 36: ...язык Lisp перестал удовлетворять многих разработчиков. И в первую очередь это происходит из-за его непосильного синтаксиса...

Синтаксис Лиспа, действительно, необычен, и обилие вложенных скобок[2] может напугать и запутать. Однако такая запись делает код неудобочитаемым только на бумаге, а это далеко не основной способ чтения программ. При использовании надлежащих инструментов, то есть компьютера и приличного текстового редактора,[3] ситуация выворачивается наизнанку: код на Лиспе исключительно прост для машинного разбора, что позволяет редактору быстро и однозначно определять границы выражений — а это и упомянутая автором подсветка скобок, и удобные и гарантированно правильные навигация и редактирование в терминах выражений и сущностей более высокого уровня — например, функций, клозов и т.п. Реализация такой функциональности для языков с богатым синтаксисом крайне затруднительна — на полноценный разбор текста программы «на лету» не хватает даже мощи современных многогигагерцевых чудовищ, и приходится ограничиваться простыми эвристиками, которые далеко не всегда дают приемлемый результат. Кроме того, принятая в Лиспе префиксная нотация вообще, на мой взгляд, гораздо удобнее традиционной инфиксной — чего стоит одно отсутствие кошмарных приоритетов операторов... Но это мелочи.

Важнее всего то, что текст программы на Лиспе — это, фактически, удобочитаемо[4] записанное дерево ее разбора,[5] а последнее является обычной структурой данных Лиспа — списком, которым можно произвольным образом манипулировать. Иными словами, программы могут писать программы.

Типизация[править | править код]

  • Стр. 37: Другой реальной проблемой языка Lisp было фактическое отсутствие типов объектов...

Лисп — язык с динамической типизацией, что вовсе не означает «фактического отсутствия типов». Просто тип в Лиспе — свойство самого объекта, а не связанного с ним имени.[6] Более того, сам тип — тоже объект, с которым можно делать все то же, что и с числами, строками, функциями и т.п.: сохранять в структурах, конструировать «на лету», сравнивать, хешировать, выводить на экран или пересылать по сети...

Программы пишут программы[править | править код]

  • Стр. 37: Хотя язык Lisp все еще используется [...] он уже не удовлетворяет некоторым современным запросам, которые заставляют разработчиков программ взваливать как можно большую ношу на компилятор...

Итак, помимо привычного любому знакомому с функциональной парадигмой программисту арсенала, у нас есть программы, являющиеся полноценными значениями, такие же типы... Добавим к этому среду исполнения, включающую в себя компилятор, интерактивный отладчик и многое другое — вроде бы, «чего же боле»? Но есть еще кое-что, дающее основание полушутливо делить языки программирования на Лисп и все остальные: макроопределения.

Макроопределения Лиспа уникальны: это не классические инструкции препроцессора, будь то убогий CPP или навороченный Camlp4, и не какой-то отдельный «язык-вещь в себе», как шаблоны C++ — это полноценные функции, выполняющиеся на этапе компиляции[7] и строящие на основе своих аргументов любые, без каких-либо ограничений выражения Лиспа.

И это еще не все. Если программисту недостаточно трансформации программы во время компиляции и выполнения — к его услугам таблицы и макроопределения чтения, позволяющие внести необходимые изменения в процесс разбора текста программы, также являющиеся полноценными значениями, доступные в любой момент etc. etc.

В результате программист получает фантастические возможности — от обычных оптимизаций вроде вычислений на этапе компиляции до возможности определять произвольные языковые конструкции, превращая Лисп в наилучшим образом соответствующий решаемой задаче проблемно-ориентированный язык, а то и в несколько, если задача того потребует (причем не придется заниматься ни написанием компиляторов, ни интеграцией полученных языков друг с другом и с самим Лиспом — все это есть изначально). Сам Лисп, что вполне естественно, уже включает в себя несколько таких специализированных языков — это, например, loop facility[8] (сверхнавороченный язык организации циклов) и CLOS (мощнейшая объектная система). Можно даже с некоторым преувеличением сказать, что правильное программирование на Лиспе заключается в создании проблемно-ориентированных языков и применении их по назначению.[9]

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

Подытоживая сказанное выше: Лисп тем и отличается от прочих, что не навязывает способы решения задач, а сам становится тем языком, который нужен программисту — и при надлежащем использовании как ни один другой язык позволяет переваливать тяготы программирования с больной головы на здоро... то есть, прошу прощения, с программиста на компилятор :]

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

Проблемы Лиспа[править | править код]

Проблемы Лиспа обусловлены в основном исторически: это и изначальное восприятие его исключительно как малополезного в «реальном мире» академического «языка искусственного интеллекта», и недостаточные до сравнительно недавнего времени ресурсы распространенных машин (Лисп потребляет память не меньше Java — но, кстати, работает гораздо быстрее[10]), и разделяемое даже просвещенным автором ошибочное представление о Лиспе как об интерпретируемом языке (Стр. 38: ...большинство функциональных языков реализуются как интерпретаторы, следуя традициям языка Lisp.)... И, конечно же, важнейшую роль играет человеческий фактор: естественная экономия мышления, увы, часто переходит в недомыслие, и рядовой кодер скорее вручную выполнит работу компилятора, чем попытается мыслить самостоятельно...

Но это уже совсем другая история. ©

Мне кажется Лисп навязывает стиль снизу-вверх: то есть последовательное написание слоёв (создание языков), создание нижнего слоя, затем более высокоуровневого. Такой подход к разработки не возможно контролировать менеджерам: менеджер в состоянии проверить только самый верхний уровень. Даже если этот подход более эффективен, но его невозможно контролировать — он не пригоден для промышленного внедрения. Хорошо это или плохо — это уже другая история.

Примечания[править | править код]

  1. Речь идет о диалекте Common Lisp.
  2. Одна из шуточных расшифровок названия языка — Lots of Insane Silly Parentheses.
  3. Это, конечно же, GNU Emacs :]
  4. Во всяком случае, по сравнению с жуткими конструкциями Template Haskell или Camlp4.
  5. На этом основании иногда утверждают, что у Лиспа вообще нет синтаксиса.
  6. Хотя можно и нужно объявлять, что такое-то имя будет связываться со значениями таких-то типов, а такое-то выражение выдаст значение вот этакого — это полезно и для безопасности, и для производительности.
  7. Важно понимать, что компилятор доступен Лисп-программе в любой момент, от разбора ее текста до времени выполнения.
  8. Кстати, яркий пример изменения синтаксиса языка: конструкция loop больше похожа на C, чем на Лисп.
  9. То, что называется language-oriented programming, самостоятельная, на мой взгляд, парадигма.
  10. Существует, впрочем экспериментальное доказательство обратного: Java завершает выполнение бесконечно-рекурсивной пустой функции менее чем за секунду, а от Лиспа этого еще никто не дождался :]