Как контролировать порядок расширения макроса Схемы?
Я работаю с расширением Racket macro syntax-id-rules
, что некоторые другие реализации Схемы предоставляют под именем identifier-syntax
, Они позволяют вам указывать расширения макросов, которые будут происходить, даже если определенный идентификатор не находится в позиции головы. Так, например:
(define hidden #f)
(define-syntax proxy
(syntax-id-rules (set!)
[(set! proxy v) (set! hidden v)]
[proxy hidden]))
установит идентификатор proxy
быть доверенным лицом для hidden
, Это бесполезный пример, но он иллюстрирует использование.
Я нахожусь в ситуации, когда мне нужен глобальный обычный макрос, давайте назовем его foo
, что я хочу переопределить в некоторых случаях, когда я использую макрос идентификатора, как proxy
, То есть я хочу иметь возможность сделать что-то вроде этого:
(define-syntax foo
(syntax-rules ()
[(foo arg ...) 'default]))
(define hidden #f)
(define-syntax proxy
(syntax-id-rules (foo set!)
[(foo proxy arg ...) 'special]
[(set! proxy v) (set! hidden v)]
[proxy hidden]))
(foo proxy) ; should return 'special
Но на самом деле последняя строка возвращает 'default
, поскольку foo
макрос раскрывается до proxy
один.
Любые идеи, как я мог бы достичь чего-то в этом направлении, но с proxy
макрос идентификатора, переопределяющий определение макроса по умолчанию для foo
? Я не привержен вышеупомянутой архитектуре специально.
Добавлено: Это не для любого реального использования, но является частью демонстрации теоретической точки в формальной семантике.
2 ответа
@soegaard объяснил это прекрасно. Вы не можете делать то, что вы хотите напрямую, без изменения макроэкспандера.
Чтобы расширить ответ @ soegaard, вот способ смоделировать то, что вы просите. По сути, это макрокоманда "двойная диспетчеризация". Однако, как отметил Согард, возможно, существует более идиоматический способ достижения того, чего вы хотите, в зависимости от ваших целей.
#lang racket
(require (for-syntax syntax/parse))
(begin-for-syntax
(define (special-condition? id)
(and (identifier? id)
(regexp-match #rx"^p" ; starts with "p"
(symbol->string (syntax->datum id))))))
(define-syntax foo
(syntax-parser
[(_ special-case arg ...)
#:when (special-condition? #'special-case)
#'(special-case 'hidden-special-case-tag arg ...)]
; else
[(_ arg ...) #''default]))
(define hidden #f)
(define-syntax proxy
(syntax-id-rules (quote set!)
[(proxy (quote hidden-special-case-tag) arg ...) 'special]
[(set! proxy v) (set! hidden v)]
[(proxy arg ...) 'other]
[proxy hidden]))
(foo non-proxy) ; => 'default
(foo proxy) ; => 'special
(proxy) ; => 'other
proxy ; => #f
(set! proxy #t)
proxy ; => #t
Мне кажется, что вам нужно найти альтернативную стратегию. Возможно, мы сможем найти решение, если вы предоставите более подробную информацию о ситуации, в которой вы хотите использовать это.
В любом случае, вот почему ваша стратегия не работает. Когда ты пишешь
(define-syntax proxy ...)
вы ассоциируете синтаксический преобразователь с идентификатором proxy
, Этот трансформатор вызывается расширителем, когда он видит (proxy ...)
, (set! proxy ...)
, или же proxy
,
Для того, чтобы контролировать то, что (foo proxy arg ...)
расширяется, вам нужно указать его в синтаксическом преобразователе, связанном с foo
,
Теперь в зависимости от ситуации могут быть хитрости, которые можно сыграть.
Например, можно представить, как обернуть вашу программу в новую форму, которая переписывает (foo proxy arg ...)
в (proxy 'was-a-foo-originally arg ...)
а затем пусть синтаксический преобразователь для proxy
справиться с остальным.
Простое решение состоит в том, чтобы переместить обработку (foo proxy arg ...)
в трансформатор для foo
, но вы специально спрашиваете решение, где foo
не изменился