Ракетка: Макросы, сгенерированные макросами, определенными как macroname.0 - PullRequest
0 голосов
/ 28 мая 2018

Я пытаюсь немного узнать о Racket и его макросистеме.Моим представлением было написание тонкой оболочки вокруг IEX stock API .Мое подготовительное чтение включало в себя Страх Макроса Грега Хендершотта, Руководство по ракеткам (в частности, раздел о макросах и макросах и сопутствующую документацию) и другие онлайн-ресурсы.

Я хотел бы иметь возможность сделать что-то вроде этого:

(iex-op* chart dividends quote)

для генерации функций iex-chart, iex-dividends и т. Д.Использование преобразователя синтаксиса показалось мне естественным, поскольку я не думаю, что смогу легко написать функцию, генерирующую функции, с чистым синтаксисом и несколькими аргументами.

Итак:

(define-syntax-rule (iex-op* op0 ...)
  (begin
    (iex-op op0) ...))

Предполагается, что работает синтаксический преобразователь iex-op:

(define iex-base-url (string->url "https://api.iextrading.com"))
(define iex-ver "1.0")

(define-syntax (iex-op stx)
  (syntax-case stx (quote) ; quote is one of the operations
    [(_ oper)
     (with-syntax ([op-name (format-id stx "iex-~a" #'oper)])
         #'(define (op-name ticker . args)
             (let ([op-url
                    (combine-url/relative
                        iex-base-url
                        (string-join
                          `(,iex-ver
                            "stock"
                            ,(symbol->string ticker)
                            ,(symbol->string (syntax-e #'oper))
                            ,(string-join args "/")) "/"))])
              (string->jsexpr (http-response-body (get http-requester op-url))))))]))

Моя проблема возникает не с макросом iex-op, который выглядит какпоступайте правильно, но с использованием iex-op*, который не делает:

Welcome to Racket 6.3
> (enter! "iex.rkt")
> (iex-op quote)
> iex-<TAB><TAB>
iex-base-url iex-op       iex-op*      iex-quote    iex-ver
> (iex-op* chart dividends)
> iex-<TAB><TAB>
iex-base-url    iex-dividends.0 iex-op*         iex-ver
iex-chart.0     iex-op          iex-quote

Функции, определенные в op*, имеют суффикс .0.Я не знаю почему, и я не могу найти документацию об этом удобно, несмотря на несколько часов поиска.

Когда я запускаю макроэкспандер в DrRacket, я обнаруживаю, что (iex-op* chart dividends) действительно расширяется до

(begin (iex-op chart) (iex-op dividends))

по желанию.Что еще хуже, когда я воспроизводю результаты преобразования синтаксиса в REPL, он работает!

> (begin (iex-op chart) (iex-op dividends))
> iex-<TAB><TAB>
iex-base-url    iex-chart.0     iex-dividends.0 iex-op*         iex-ver
iex-chart       iex-dividends   iex-op          iex-quote

Чего мне не хватает?Я с готовностью признаю, что мой код, вероятно, нуждается в некоторой существенной очистке (я медленно изгибаю свой ум Python / C / etc.), Но я скорее меньше беспокоюсь о его эстетике и больше о том, что arcanum вызывает это поведение.

1 Ответ

0 голосов
/ 28 мая 2018

Суффикс .0 на самом деле не является суффиксом имени сгенерированной привязки - он указывает, что идентификатор был введен макросом и находится внутри области действия макроса.Основная проблема заключается в том, что вы используете format-id.

. Помните, что макросистема Racket гигиеническая .Это означает, что привязки, введенные макросами, могут быть видны только макросами.Это делает автозаполнение в REPL немного более сложным, и я не думаю, что REPL делает здесь, честно говоря, очень полезный выбор.Тем не менее, вы, вероятно, хотите знать, как исправить свой код, а не тонкости того, почему REPL показывает введенные макросы привязки с суффиксом .0.

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

При непосредственном использовании iex-op, например, путем записи (iex-op quote), затем идентификатора quoteпроисходит из той же лексической области, что и форма (iex-op ....).Поэтому, когда вы вызываете format-id и даете ей форму (iex-op ....) в качестве первого аргумента, вы все равно получаете идентификатор, который разделяет ту же лексическую область, что и идентификатор quote, и все в порядке.

Однако , когда вы используете макрос iex-op*, он пропускает синтаксический объект op0, но окружающая форма (iex-op ....) приходит из макроса iex-op* .Это означает, что stx теперь относится к области видимости внутри макроса iex-op*, не к области действия идентификатора op0 .Чтобы исправить это, измените ваш вызов на format-id, чтобы он создавал идентификатор с той же областью, что и идентификатор операции, а не окружающий объект синтаксиса:

(format-id #'oper "iex-~a" #'oper)

Теперь ваш макрос будет работать так, как задумано.

Перед тем, как закончить, спросите себя: какие здесь есть вынос?Вот пара:

  1. Нарушение гигиены является тонким.Когда вы решите написать негигиеничный макрос (а format-id негигиеничен), очень задумайтесь над тем, откуда вы получаете лексический контекст.Подумайте, что произойдет, когда пользователи напишут макросы поверх вашего макроса.

    В этом случае вам повезло, что вы являетесь автором iex-op и iex-op*, поэтому вы можете исправить iex-op после того, как выстолкнулся с проблемой с ним.Но если бы вы не были автором iex-op, только iex-op*, вы оказались бы в гораздо более сложной ситуации.Подумайте об этом всякий раз, когда вы пишете какой-либо негигиеничный макрос, чтобы избежать подобных проблем в будущем.

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

Возможно, некоторые примеры в Fear of Macros, которые используют format-id,небрежно в этом отношении.Возможно, стоит попытаться улучшить их, чтобы показать лучший пример.

...