Должны ли макросы иметь побочные эффекты?

Может (или должно) расширение макроса иметь побочные эффекты? Например, вот макрос, который на самом деле идет и захватывает содержимое веб-страницы во время компиляции:

#lang racket

(require (for-syntax net/url))
(require (for-syntax racket/port))

(define-syntax foo
  (lambda (syntx)
    (datum->syntax #'lex
                   (port->string
                     (get-pure-port
                       (string->url
                         (car (cdr (syntax->datum syntx)))))))))

Затем я могу сделать (foo "http://www.pointlesssites.com/") и он будет заменен на "\r\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"\r\n\t <and so on>"

Это хорошая практика или нет? Гарантирую ли я, что Racket запустит этот код только один раз? Если я добавлю в макрос строку (display "running..."), он напечатает только один раз, но мне бы не хотелось обобщать один пример...

PS - причина, по которой я спрашиваю, в том, что я действительно думаю, что иногда это может быть действительно полезно. Например, это — это библиотека, которая позволяет вам загружать (во время компиляции) документ обнаружения из Google API Discovery и автоматически создавать для него обертки. Я думаю, было бы очень здорово, если бы библиотека действительно получала документ обнаружения из Интернета, а не из локального файла.

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


person Ord    schedule 26.10.2012    source источник
comment
Ладно, сумасшедший. Я только что понял, что библиотека обнаружения документов, с которой я связался, читает файлы во время компиляции, что является еще одной формой побочного эффекта. Это не обязательно означает, что это хорошо, но это делается в реальных библиотеках, так что...   -  person Ord    schedule 26.10.2012
comment
Что такое побочный эффект, мне больше всего нравится ваш пример запоминания результатов. (В отличие от примера чтения файла, локального или удаленного. Чтение документа обнаружения JSON во время компиляции на самом деле не так уж отличается от чтения файла .RKT. На самом деле я почти пошел в этом направлении для документов обнаружения, за исключением того, что я хотел, чтобы они работали как есть, вместо того, чтобы добавлять строку #lang. Но тот факт, что это могло работать именно так — документ обнаружения JSON мог быть языком — очень крутая вещь. о ракетке.)   -  person Greg Hendershott    schedule 30.10.2012
comment
Я так люблю. Задайте вопрос, связанный с библиотекой, и автор этой библиотеки небрежно оставит вам комментарий :) Я все еще только изучаю все функции расширения/сборки языка Racket, но чем больше я узнаю, тем больше я поражаюсь.   -  person Ord    schedule 30.10.2012


Ответы (2)


Краткий ответ

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

Более длинный ответ

Макросы с побочными эффектами являются мощным инструментом и могут позволить вам делать то, что значительно упрощает написание программ, или делать то, что вообще невозможно. Но есть подводные камни, о которых следует помнить, когда вы используете побочные эффекты в макросах. К счастью, Racket предоставляет все инструменты, чтобы убедиться, что вы можете сделать это правильно.

Самый простой вид побочного эффекта макроса — это когда вы используете какое-то внешнее состояние для поиска кода, который хотите сгенерировать. Примеры, которые вы перечисляете в вопросе (чтение описания API Google), относятся к этому типу. Еще более простой пример — макрос include:

#lang racket
(include "my-file.rktl")

Это считывает содержимое myfile.rktl и размещает его прямо там, где используется форма include.

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

Другой простой пример, который не очень хорош, выглядит примерно так:

#lang racket
(define-syntax (show-file stx)
  (printf "using file ~a\n" (syntax-source stx))
  #'(void))

(show-file)

Это связано с тем, что printf выполняется только во время компиляции, поэтому, если вы скомпилируете свою программу, которая использует show-file заранее (как с raco make), тогда printf произойдет тогда, а не во время запуска программы, что, вероятно, не так. т намерение.

К счастью, в Racket есть техника, позволяющая эффективно писать макросы типа show-file. Основная идея состоит в том, чтобы оставить остаточный код, который фактически выполняет побочный эффект. В частности, для этой цели вы можете использовать форму Racket begin-for-syntax. Вот как я бы написал show-file:

#lang racket
(define-syntax (show-file stx)
  #`(begin-for-syntax
      (printf "using file ~a\n" #,(syntax-source stx))))

(show-file)

Теперь вместо расширения макроса show-file printf происходит в коде, который show-file создает, с исходным кодом, встроенным в расширенный синтаксис. Таким образом, ваша программа будет работать корректно даже при предварительной компиляции.

Есть и другие варианты использования макросов с побочными эффектами. Одним из наиболее заметных в Racket является взаимодействие между модулями — поскольку require не создает значений, которые может получить требующий модуль, наиболее эффективным способом взаимодействия между модулями является использование побочных эффектов. Чтобы заставить это работать при наличии компиляции, требуется почти такой же трюк с begin-for-syntax.

Это тема, над которой сообщество Racket, и я в частности, много думали, и есть несколько академических статей, в которых рассказывается, как это работает:

Компонуемые и компилируемые макросы: нужно когда?< /a>, Мэтью Флэтт, ICFP 2002

Расширенная макрология и реализация типизированной схемы< /a>, Райан Калпеппер, Сэм Тобин-Хохштадт и Мэтью Флэтт, Scheme Workshop 2007

Языки как библиотеки, Сэм Тобин-Хохштадт, Райан Калпеппер, Винсент Сент-Амур, Мэтью Флэтт и Матиас Феллейзен, PLDI 2011

person Sam Tobin-Hochstadt    schedule 28.10.2012
comment
Не могли бы вы объяснить begin-for-syntax немного подробнее? Я просмотрел документацию [docs.racket-lang.org/reference/, но я не совсем понял. - person Ord; 30.10.2012
comment
В Руководстве по Racket есть более простое для понимания обсуждение: docs.racket-lang. .org/guide/stx-phases.html (и раздел после него). - person Sam Tobin-Hochstadt; 30.10.2012
comment
Ладно, думаю, я понял. Спасибо! - person Ord; 30.10.2012

В обычном Лиспе функция eval-when позволяет вам решить, когда макрос будет раскрыт.

person danielrupis    schedule 02.11.2012