Ленивая оценка против макросов - PullRequest
46 голосов
/ 13 августа 2011

Я привык к ленивым вычислениям от Haskell, и теперь меня раздражают языки с нетерпением по умолчанию теперь, когда я правильно использовал ленивые вычисления. Это на самом деле довольно вредно, так как другие языки, которые я использую, в основном делают ленивые вычисления очень неудобными, обычно включая развертывание пользовательских итераторов и так далее. Так что, просто приобретя некоторые знания, я на самом деле сделал себя на 1001 * менее продуктивным на своих оригинальных языках. Вздох.

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

Я баловался с макросами в разных вариантах Lisp. Они просто казались по-настоящему организованным способом копирования и вставки кусков кода во время компиляции. Они, конечно, не были святым Граалем, о котором Лисперсу нравится думать. Но это почти наверняка, потому что я не могу использовать их должным образом. Конечно, наличие макросистемы, работающей с той же базовой структурой данных, с которой собран сам язык, действительно полезно, но это все же в основном организованный способ копирования и вставки кода. Я признаю, что основание системы макросов на том же AST, что и язык, который допускает полное изменение времени выполнения, является мощным.

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

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

Ответы [ 5 ]

60 голосов
/ 13 августа 2011

Ленивая оценка делает макросы избыточными

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

Макросы предназначены для расширения языка новыми синтаксическими формами.Некоторые из специфических возможностей макросов:

  1. Влияние на порядок, контекст и т. Д. При оценке выражений.
  2. Создание новых форм привязки (т. Е. Влияющих на scope выражение вычисляется в).
  3. Выполнение вычислений во время компиляции, включая анализ кода и преобразование.

Макросы, которые выполняют (1), могут быть довольно простыми.Например, в Racket форма обработки исключений with-handlers - это просто макрос, который расширяется до call-with-exception-handler, некоторых условий и некоторого кода продолжения.Он используется так:

(with-handlers ([(lambda (e) (exn:fail:network? e))
                 (lambda (e)
                   (printf "network seems to be broken\n")
                   (cleanup))])
  (do-some-network-stuff))

Макрос реализует понятие «предложения предиката и обработчика в динамическом контексте исключения» на основе примитива call-with-exception-handler, который обрабатывает все исключения в точке, в которой они'1027 *

Более сложное использование макросов - реализация генератора синтаксических анализаторов LALR (1) .Вместо отдельного файла, который требует предварительной обработки, форма parser - это просто другое выражение.Он принимает описание грамматики, вычисляет таблицы во время компиляции и создает функцию синтаксического анализа.Процедуры действий имеют лексическую область видимости, поэтому они могут ссылаться на другие определения в файле или даже на переменные, связанные с lambda.Вы даже можете использовать другие языковые расширения в подпрограммах действий.

В крайнем случае, Typed Racket - это типизированный диалект Racket, реализованный с помощью макросов.Он имеет сложную систему типов, разработанную для соответствия идиомам кода Racket / Scheme, и взаимодействует с нетипизированными модулями, защищая типизированные функции с помощью динамических программных контрактов (также реализованных с помощью макросов).Он реализован с помощью макроса «typed module», который расширяет, проверяет тип и преобразует тело модуля, а также вспомогательные макросы для присоединения информации о типе к определениям и т. Д.

FWIW, есть также Lazy Racket, ленивый диалект Ракетки.Это не реализовано путем превращения каждой функции в макрос, но путем привязки lambda, define и синтаксиса приложения функции к макросам, которые создают и заставляют обещания.

В итоге, ленивая оценка и макросы имеютмаленькая точка пересечения, но это совершенно разные вещи.И макросы, конечно, не относятся к ленивой оценке.

23 голосов
/ 14 августа 2011

Лень денотатив , а макросы - нет. Точнее, если вы добавите нестрогость к денотативному языку, результат все равно будет денотативным, но если вы добавите макросы, результат не будет денотативным. Другими словами, значение выражения в ленивом чистом языке является функцией исключительно значений компонентных выражений; в то время как макросы могут давать семантически отличные результаты от семантически равных аргументов.

В этом смысле макросы более мощные, в то время как лень, соответственно, семантически более хорошо себя ведет.

Редактировать : точнее, макросы не денотативны за исключением относительно идентичности / тривиального обозначения (где понятие «денотатив» становится пустым).

23 голосов
/ 13 августа 2011

Ленивая оценка может заменить некоторые виды использования макросов (которые задерживают оценку для создания управляющих конструкций), но обратное не совсем верно. Вы можете использовать макросы для повышения прозрачности отложенных вычислений - см. SRFI 41 (Streams) для примера того, как: http://download.plt -scheme.org / doc / 4.1.5 / html / srfi-std / srfi- 41 / SrfI-41.html

Кроме того, вы также можете написать свои собственные ленивые примитивы ввода / вывода.

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

10 голосов
/ 13 августа 2011

Лисп начался в конце 50-х годов прошлого тысячелетия. См. РЕКУРСИВНЫЕ ФУНКЦИИ СИМВОЛИЧЕСКИХ ВЫРАЖЕНИЙ И ИХ ВЫЧИСЛЕНИЯ НА МАШИНЕ . Макросы были , а не частью этого Лиспа. Идея заключалась в том, чтобы вычислить с помощью символических выражений, которые могут представлять все виды формул и программ: математические выражения, логические выражения, предложения на естественном языке, компьютерные программы, ...

Позднее были изобретены макросы Lisp, и они являются приложением этой идеи к самому Lisp: макросы преобразовывают выражения Lisp (или Lisp-подобные) в другие выражения Lisp, используя полный язык Lisp в качестве языка преобразования.

Вы можете себе представить, что с помощью макросов вы можете реализовать мощные препроцессоры и компиляторы как пользователь Lisp.

Типичный диалект Lisp использует строгую оценку аргументов: все аргументы функций оцениваются до выполнения функции. Лисп также имеет несколько встроенных форм, которые имеют разные правила оценки. IF такой пример. В Common Lisp IF - это так называемый специальный оператор .

Но мы можем определить новый Лисп-подобный (под) язык, который использует ленивую оценку, и мы можем написать Макросы для преобразования этого языка в Лисп. Это приложение для макросов, но далеко не единственное.

Примером (относительно старым) такого расширения Lisp, в котором используются макросы для реализации преобразователя кода, который обеспечивает ленивую оценку структур данных, является расширение SERIES для Common Lisp.

5 голосов
/ 13 августа 2011

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

Если программирование похоже на игру с кубиками LEGO, с помощью макросов вы также можете изменить форму кубиков или материал, который они используют.построен с.

Макросы это больше, чем просто отложенная оценка.Это было доступно как fexpr (предвестник макроса в истории lisp).Макросы - это переписывание программ, где fexpr - это особый случай ...

В качестве примера рассмотрим, что в свободное время я пишу крошечный компилятор lisp to javascript и изначально (в ядре javascriptУ меня была только лямбда с поддержкой &rest аргументов.Теперь есть поддержка ключевых слов и аргументов, потому что я переопределил значение лямбды в самом lisp.

Теперь я могу написать:

(defun foo (x y &key (z 12) w) ...)

и вызвать функцию с помощью

(foo 12 34 :w 56)

При выполнении этого вызова в теле функции параметр w будет привязан к 56, а параметр z к 12, поскольку он не был передан.Я также получу ошибку во время выполнения, если в функцию передан неподдерживаемый аргумент ключевого слова.Я мог бы даже добавить некоторую поддержку проверки во время компиляции, переопределив, что означает компиляция выражений (т.е. добавив проверки, если «статические» формы вызова функций передают в функции правильные параметры).

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

Синтаксис важен (даже если технически возможно просто использовать машину Тьюринга).Синтаксис формирует ваши мысли.Макросы (и чтение макросов) дают вам полный контроль над синтаксисом.

Ключевым моментом является то, что код перезаписи кода не использует искаженный тупой язык типа brainf ** k, как метапрограммирование шаблона C ++ (где просто* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * if - это удивительное достижение), или с еще более тупым механизмом замены, чем "regexp", как препроцессор C.

Код переписывания кода использует тот же самый полноценный (и расширяемый) язык.Это все шутки; -)

Конечно, писать макросы сложнее, чем писать обычный код;но это «существенная сложность» проблемы, а не искусственная сложность, потому что вы вынуждены использовать тупой полуязык, как при метапрограммировании C ++.

Написание макросов сложнее, потому что код сложная вещь, и когдапри написании макросов вы пишете сложные вещи, которые сами создают сложные вещи.Даже не так уж редко подниматься на один уровень выше и писать макросы, генерирующие макросы (вот откуда взялась старая шутка «Я пишу код, пишущий код, за который мне платят»).

Но макроэкономика просто безгранична.

...