Расширение макроса схемы: вложение let-синтаксиса в define-синтаксис
Я хочу расширить
(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
быть неопределенным идентификатором. Я предполагаю, что это проблема в области видимости, возможен ли этот тип макроразложения?
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
чтобы увидеть, что все формы преобразованы.)
Тем не менее, если вы хотите глобальный вид f
для использования внутри foo
тогда вы действительно должны сделать именно это: определить глобальный 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
к тому же бесполезному макросу, используя define-syntax-parameter
, а затем использовать syntax-parameterize
"настроить" его значение внутри 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)))