Подтвердить что ты не робот

Lazy Evaluation против Macros

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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

  • Влияет на порядок, контекст и т.д. оценки выражения.
  • Создание новых форм привязки (т.е. влияние на область, в которую вычисляется выражение).
  • Выполнение вычисления времени компиляции, включая анализ и преобразование кода.

Макросы, которые делают (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, который обрабатывает все исключения в точке, в которой они были подняты.

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

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

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

Таким образом, ленивая оценка и макросы имеют небольшую точку пересечения, но они очень разные. И макросы, конечно, не относятся к ленивой оценке.

Ответ 3

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

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

Изменить: точнее, макросы не денотативны, кроме как по отношению к тождественному/тривиальному значению (где понятие "денотативный" становится пустым).

Ответ 4

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

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

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

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

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

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

Ответ 5

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

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

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

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

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

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

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

(foo 12 34 :w 56)

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

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

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

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

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

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

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

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