Расширение макроса схемы: вложение let-синтаксиса внутри синтаксиса определения

Я хочу расширить

(foo x (f n) (f n) (arbitrary) (f n) ...)

в

(begin (x 'f n) (x 'f n) (arbitrary) (x 'f n) ...)

моя попытка:

(define-syntax foo
  (syntax-rules ()
    ((_ l a ...)
     (let-syntax ((f (syntax-rules ()
                       ((_ n) (l (quote f) n)))))
       (begin a ...)))))

(define (x t1 t2) (cons t1 t2))   ;; for example only
(define (arbitrary) (cons 'a 'b)) ;; for example only
(foo x (f 1) (f 2) (arbitrary) (f 3))

Используя шаговый макрос, я вижу, что первый этап макроса расширяется до

(let-syntax ((f (syntax-rules () ((_ n) (x 'f n)))))
  (begin (f 1) (f 2) (arbitrary) (f 3)))

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


person kjfletch    schedule 24.01.2012    source источник


Ответы (1)


Да, вам нужно получить f откуда-то где-нибудь - ваш макрос просто создает это, и поэтому он не виден пользователям foo. Когда вы считаете, что вам нужно его откуда-то получить, вопрос в том, откуда бы вы это взяли? Вот фиксированная версия вашего кода, в которой предполагается, что это первое, что есть во второй подчиненной форме foo:

(define-syntax foo
  (syntax-rules ()
    [(_ l (f a) more ...)
     (let-syntax ([f (syntax-rules ()
                       [(_ n) (l 'f n)])])
       (list (f a) more ...))]))

(define (x t1 t2) (cons t1 t2))
(define (arbitrary) (cons 'a 'b))
(foo x (f 1) (f 2) (arbitrary) (f 3))

(Я также расширил его до list, чтобы увидеть, что все формы преобразованы.)

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

;; no body => using `f' is always an error
(define-syntax f (syntax-rules ()))

(define-syntax foo
  (syntax-rules ()
    [(_ l a ...) (list (foo-helper l a) ...)]))
(define-syntax foo-helper
  (syntax-rules (f) ; match on f and transform it
    [(_ l (f n)) (l 'f n)]
    [(_ l a)     a]))

(define (x t1 t2) (cons t1 t2))
(define (arbitrary) (cons 'a 'b))
(foo x (f 1) (f 2) (arbitrary) (f 3))

Основное ограничение в том, что это будет работать, только если одна из a форм использует f, но не будет работать, если она вложена в выражение. Например, это вызовет синтаксическую ошибку:

(foo x (f 1) (f 2) (arbitrary)
       (let ([n 3]) (f n)))

Вы можете представить себе усложнение foo-helper и заставить его рекурсивно сканировать входные данные, но это скользкая дорожка, на которую вы не хотите попасть. (Вам нужно будет сделать особые случаи для таких мест, как внутри quote, в привязке и т. Д.)

Чтобы решить эту проблему в Racket (а в последнее время и в Guile), используйте параметр синтаксиса < / а>. Подумайте об этом как о привязке f к тому же бесполезному макросу с помощью _ 16_, а затем используйте _ 17_ для" корректировки "его значения внутри foo макроса это делает преобразование, которое вы хотите. Вот как это выглядит:

;; needed to get syntax parameters
(require racket/stxparam)

;; same useless definition, but as a syntax parameter
(define-syntax-parameter f (syntax-rules ()))

(define-syntax foo
  (syntax-rules ()
    [(_ l a ...)
     ;; adjust it inside these forms
     (syntax-parameterize ([f (syntax-rules ()
                                [(_ n) (l 'f n)])])
       (list a ...))]))

(define (x t1 t2) (cons t1 t2))
(define (arbitrary) (cons 'a 'b))
(foo x (f 1) (f 2) (arbitrary)
       (let ([n 3]) (f n)))
person Eli Barzilay    schedule 24.01.2012
comment
Я надеялся, что тело вызова макроса верхнего уровня будет передано как есть и оценено в рамках расширенной формы, в которой f действительно существует. Предлагаемое вами решение не является идеальным, поскольку я предполагаю, что тело foo будет содержать смесь произвольных выражений и известных выражений ключевых слов (например, f). Вы, вероятно, можете сказать, что я пытаюсь сделать более понятный интерфейс для традиционного диспетчера, возможно, мне придется просто положиться на диспетчер. Спасибо. - person kjfletch; 25.01.2012
comment
Теперь я понимаю, что это вопрос макрогигиены, а не масштабов. f - это не известная привязка, поэтому для внутреннего использования создается новая гигиеническая привязка. - person kjfletch; 25.01.2012
comment
Макрогигиена - это проблема масштаба: если вам нужен всемирно известный f, то вам нужно ... сделать именно это. Я добавил два примера для этого. - person Eli Barzilay; 25.01.2012