Для тех из вас, кто имел опыт работы как с Haskell, так и с некоторыми разновидностями Lisp, мне любопытно, насколько «приятно» (если использовать ужасный термин) писать код на Haskell и Lisp.
Немного предыстории: сейчас я изучаю Haskell, раньше работал со Scheme и CL (и немного углубился в Clojure). По традиции вы можете считать меня поклонником динамических языков за лаконичность и скорость, которые они обеспечивают. Я быстро влюбился в макросы Lisp, поскольку они дали мне еще один способ избежать многословия и шаблонности.
Я нахожу Haskell невероятно интересным, поскольку он знакомит меня со способами программирования, о существовании которых я не подозревал. В нем определенно есть некоторые аспекты, которые, как кажется, могут помочь в достижении гибкости, например, простота написания частичных функций. Однако меня немного беспокоит потеря макросов Lisp (я предполагаю, что теряю их; правда, я, возможно, просто еще не узнал о них?) И систему статической типизации.
Может ли кто-нибудь, кто сделал приличное количество кода в обоих мирах, прокомментировать, чем отличается опыт, что вы предпочитаете, и если это предпочтение является ситуативным?





Прежде всего, не беспокойтесь о потере определенных функций, таких как динамический набор текста. Поскольку вы знакомы с Common Lisp, чрезвычайно хорошо разработанным языком, я предполагаю, что вы знаете, что язык не может быть сведен к его набору функций. Все дело в едином целом, не так ли?
В этом отношении Haskell сияет так же ярко, как и Common Lisp. Сочетание его функций дает вам способ программирования, делающий код чрезвычайно коротким и элегантным. Отсутствие макросов несколько смягчается более сложными (но также сложными для понимания и использования) концепциями, такими как монады и стрелки. Система статических типов увеличивает ваши возможности, а не мешает вам, как в большинстве объектно-ориентированных языков.
С другой стороны, программирование на Haskell намного менее интерактивно, чем на Lisp, и огромное количество отражений, присутствующих в таких языках, как Lisp, просто не соответствует статическому взгляду на мир, который предполагает Haskell. Поэтому доступные вам наборы инструментов для двух языков сильно различаются, но их трудно сравнивать друг с другом.
Я лично предпочитаю Lisp-метод программирования в целом, так как я считаю, что он лучше подходит для моей работы. Однако это не означает, что вы тоже обязаны это делать.
@JohannesGerer: Я не пробовал, но, насколько я прочитал, GHCi не является оболочкой в работающем образе, где вы можете переопределить и расширить произвольные части всей программы во время ее работы. Кроме того, синтаксис Haskell значительно усложняет программное копирование фрагментов программы между repl и редактором.
Короткий ответ:
[Примечание: существует «Шаблон Haskell», который позволяет вам писать макросы так же, как в Lisp, но, строго говоря, вы никогда не должны нужно его.]
От Конора Макбрайда, цитируемого Доном Стюартом: «Мне нравится думать, что типы искажают нашу гравитацию, так что направление, в котором мы должны двигаться [для написания правильных программ], становится« под гору ». Система типов позволяет на удивление легко писать правильные программы… см. эта почта и его повторные публикации.
Функции высокого порядка не могут заменить макросы, и, фактически, в CL почему-то есть и то, и другое. Настоящая сила макросов в CL заключается в том, что они позволяют разработчику вводить новые языковые функции, которые помогают лучше выразить решение проблемы, не дожидаясь выхода новой версии языка, как в Haskell или Java. Например, если бы у Haskell была такая возможность, авторам Haskell не нужно было бы писать расширения GHC, если бы они могли быть реализованы самими разработчиками в виде макросов в любое время.
@mljrg У вас есть конкретный пример? См. Комментарии к ответу Hibou57 ниже, где предполагаемый пример оказался сомнительным. Мне было бы интересно узнать, что вы имеете в виду (например, код Haskell с макросами и без них).
Уберите каррирование из Haskell. Не могли бы вы реализовать это с тем, что осталось бы в Haskell? Другой пример: предположим, что Haskell не поддерживает сопоставление с образцом, не могли бы вы добавить его самостоятельно, чтобы разработчики GHC не поддерживали его? В CL вы можете использовать макросы для расширения языка по желанию. Я предполагаю, что именно поэтому CL язык не изменился со времени своего стандарта еще в 90-х, тогда как Haskell, похоже, имеет нескончаемый поток расширений в GHC.
По мере того как я продолжаю свое путешествие по изучению Haskell, мне кажется, что одна вещь, которая помогает «заменить» макросы, - это способность определять свои собственные инфиксные операторы и настраивать их приоритет и ассоциативность. Довольно сложная, но интересная система!
В Haskell меньше необходимости в метапрограммировании, чем в Common Lisp, потому что многое можно структурировать вокруг монад, а добавленный синтаксис делает встроенные DSL менее древовидными, но всегда есть Template Haskell, как упоминалось в ShreevatsaR, и даже Лискелл (семантика Haskell + Lisp синтаксис), если вам нравятся круглые скобки.
Liskell ссылка на сайт мертв, но в наши дни есть Hackett.
В Haskell вы можете определить функцию if, что невозможно в LISP. Это возможно из-за лени, которая делает программы более модульными. В этой классической статье: Почему важна FP Джона Хьюза объясняется, как лень улучшает возможность компоновки.
Схема (один из двух основных диалектов LISP) действительно имеет ленивое вычисление, хотя это не по умолчанию, как в Haskell.
(defmacro doif (x y) `(если, x, y))
Макрос - это не то же самое, что функция - макросы плохо работают с функциями высшего порядка, такими как fold, например, тогда как нестрогие функции делать.
Я программист на Common Lisp.
Некоторое время назад я попробовал Haskell, и в итоге я решил придерживаться CL.
Причины:
У Haskell, конечно, есть свои достоинства, и некоторые вещи он делает принципиально по-другому, но в долгосрочной перспективе он меня не решает.
Эй, у вас случайно есть название той статьи Костанцы, на которую вы ссылались? Похоже, этот файл был перемещен.
Возможно, это был такой: p-cos.net/documents/dynatype.pdf
Обратите внимание, что haskell также поддерживает синтаксис префикса, но я бы сказал, что использование monad >> = было бы очень некрасивым. Также я не согласен с тем, что нечистота является благословением: P
Мне нравится это примечание: Мы еще не собрали эмпирических данных, вызывает ли эта проблема серьезные проблемы в реальных программах.
Ни один из примеров в этой статье (Pascal Costanza, Динамический и статический набор текста - анализ на основе шаблонов) не применим к Haskell. Все они специфичны для Java (или, точнее, специфичны для "объектно-ориентированное программирование"), и я не вижу ни одной из этих проблем, возникающих в Haskell. Точно так же все ваши другие аргументы спорны: можно также сказать, что Haskell «чистый и, следовательно, больше подходит для быстрого прототипирования», что синтаксис префикса не является обязательным, что у него нет большого количества компиляторов, которые делают разные вещи , так далее.
Эта статья действительно почти не имеет отношения к Haskell. "dilbert = dogbert.hire(dilbert);" ?? Я сомневаюсь, что многие программисты на Haskell могут даже прочитать это, не вздрогнув.
Есть действительно интересные вещи, которых вы можете достичь в Лиспе с помощью громоздких (если возможно) макросов в Haskell. Возьмем, к примеру, макрос «memoize» (см. Главу 9 PAIP Питера Норвига). С его помощью вы можете определить функцию, скажем foo, а затем просто оценить (memoize 'foo), которая заменяет глобальное определение foo мемоизированной версией. Можете ли вы добиться того же эффекта в Haskell с помощью функций высшего порядка?
Не совсем (AFAIK), но вы можете сделать что-то подобное, изменив функцию (при условии, что она рекурсивна), чтобы функция рекурсивно вызывалась в качестве параметра (!), А не просто вызывала себя по имени: haskell.org/haskellwiki/Memoization
Вы можете добавить foo в ленивую структуру данных, где значение будет сохранено после вычисления. Фактически это будет то же самое.
Все в Haskell запомнено и, вероятно, встроено, когда это необходимо по умолчанию компилятором Haskell.
Что касается макросов, вот страница, на которой об этом говорится: Привет, Haskell, прощай, Лисп. Это объясняет точку зрения, согласно которой макросы в Haskell просто не нужны. Для сравнения прилагается небольшой пример.
Пример случая, когда требуется макрос LISP, чтобы избежать оценки обоих аргументов:
(defmacro doif (x y) `(if ,x ,y))
Пример случая, когда Haskell не оценивает систематически оба аргумента без необходимости в каком-либо макроопределении:
doif x y = if x then (Just y) else Nothing
И вуаля
Это распространенное заблуждение. Да, в Haskell лень означает, что вам не нужны макросы, когда вы не хотите оценивать некоторые части выражения, но это только самое тривиальное подмножество всех применений макросов. Google для "Свинья до Perl" за доклад, демонстрирующий макрос, который нельзя сделать ленивым. Кроме того, если вы делать хотите, чтобы какой-то бит был строгим, вы не можете сделать это как функцию - отражая тот факт, что delay Scheme не может быть функцией.
@ Эли Барзилай: Я не считаю этот пример очень убедительным. Вот полный, простой перевод слайда 40 на Haskell: pastebin.com/8rFYwTrE
@ Рид Бартон: А? Основная идея этого документа - создание макрос, который на самом деле является небольшим DSL для определения автоматов, которые "компилируются" в код схемы. Ваш код, OTOH, является своего рода простым переводом кода, но (а) он использует поиск по таблице, о котором Шрирам говорит в начале, и, что гораздо важнее, (б) вы используете простой Haskell, и результат все еще далек от определения такого DSL. AFAICT, единственное, что это демонстрирует, это «легко написать такой код, проще, когда вы можете использовать значения функций в таблице». То есть, к макросам мало что относится.
@ Эли Барзилай: Я вообще не понимаю вашего ответа. acceptявляется (E) DSL. Функция accept является аналогом макроса, описанного на предыдущих страницах, а определение v точно аналогично определению v на схеме на слайде 40. Функции Haskell и Scheme вычисляют то же самое с той же стратегией оценки. В лучшем случае макрос позволяет вам раскрыть оптимизатору большую часть структуры вашей программы. Вряд ли вы можете назвать это примером, когда макросы увеличивают выразительную силу языка так, как это не воспроизводится ленивым вычислением.
Я не слежу ни за чем из этого. Прежде всего, да - accept - это функция, которая выполняет работу, но это не DSL, это функция, как и все другие функции, и такие вещи, как использование ее во всех подсписках или необходимое использование where с его собственная область видимости - это именно то, что делает макрос ненужным. Что касается ленивой оценки - вы не используете ее каким-либо существенным образом, поэтому я не понимаю, насколько уместен весь этот аргумент.
@Eli Barzilay: В гипотетической ленивой схеме вы могли бы написать это: pastebin.com/TN3F8VVE Мое общее утверждение состоит в том, что этот макрос мало что вам дает: немного другой синтаксис и более простое время для оптимизатора (но это не имеет значения для «достаточно умного» компилятор "). Взамен вы загнали себя в ловушку невыразительного языка; как определить автомат, который соответствует любой букве, не перечисляя их все? Кроме того, я не знаю, что вы имеете в виду, говоря «использовать его во всех подсписках» или «обязательное использование where с его собственной областью действия».
Рид: (а) ленивый план не является гипотетическим - один из них был частью Racket в течение нескольких лет; (б) факт, что макросы все еще полезны, есть хорошая подсказка; (c) то, что вы написали, также показывает, почему макрос полезен - он не использует его и, следовательно, не является тем DSL, о котором говорит Шрирам; (г) под «использованием его в подсписках» и т. д. я имел в виду, что у вас есть определенные требования к вашему «DSL», которые исходят от реализации (например, использование accept) - это одна из причин, почему это нет как DSL;
(e) Иллюзия того, что лень делает макросы управления потоком (те, которые не имеют новых привязок) избыточными, может рассматриваться как фикция, если вы думаете о добавлении строгого оператора к ленивому языку - для использования такого оператора требуются специальные формы (и макросы ) тоже; (е) еще один момент: если в Haskell макросы не нужны, почему они у него есть? (И даже до TH использовался CPP.)
Наконец, (ж) убедитесь, что вы ловите себя на «невыразительном языке» - все дело в DSL, а не в GPL. Очевидно, что можно написать более сложный макрос, который будет иметь выражения Scheme (например, предикат для символа, вместо того, чтобы перечислять их все), но в этом примере это выходит за рамки DSL.
Хорошо, я сдаюсь. По-видимому, ваше определение DSL - это «аргументы макроса», и поэтому мой пример с ленивой схемой не является DSL, несмотря на то, что он синтаксически изоморфен оригиналу (automaton становится letrec, : становится accept, -> становится ничем в этой версии). Что бы ни.
Ссылка в этом посте кажется неработающей @ Hibou57, не могли бы вы проверить и при необходимости исправить свое сообщение?
@batbrat, исправлено, и спасибо, что уведомили меня о проблеме (они изменили расположение своих URL-адресов на этом сайте).
Не могли бы вы подробнее рассказать о «программировании на Haskell гораздо менее интерактивно». Разве GHCi не предоставляет все, что вам нужно?