В чем разница между датум-> синтаксис и синтаксис #'в теле определения-синтаксиса?
Тестовый код:
(define-syntax (test-d stx)
#'(begin
(define (callme a)
(writeln a))))
(define-syntax (test-e stx)
(datum->syntax stx '(begin
(define (callme2 a)
(writeln a)))))
> (test-d)
> (callme 1)
. . callme: undefined;
cannot reference an identifier before its definition
> (test-e)
> (callme2 1)
1
Я не понимаю разницы в test-d и test-e. Для меня они выглядят равными. Тем не менее, callme не определена.
Даже макро-степпер говорит, что это то же самое.
Expansion finished
(module anonymous-module racket
(#%module-begin
(define-syntax (test-d stx)
#'(begin (define (callme a) (writeln a))))
(define-syntax (test-e stx)
(datum->syntax
stx
'(begin (define (callme2 a) (writeln a)))))
(begin (define (callme a) (writeln a)))
(begin (define (callme2 a) (writeln a)))))
Я думаю, что в test-d
отсутствует некоторая информация (контекст?), которая передается в test-e
через stx
,
Как я могу достичь callme
определяется также с использованием только #'?
1 ответ
Макросистема Ракетки гигиенична. Это означает, что идентификаторы, представленные макросом, живут в своей собственной области видимости - они не конфликтуют с идентификаторами, используемыми или определенными вне макроса. Обычно это то, что вам нужно, поскольку это позволяет избежать проблем, когда и автор макроса, и пользователь макроса решают использовать одинаковые имена переменных.
В вашем случае, однако, вы хотите поведение, которое явно негигиенично. Вы хотите, чтобы макрос определял новый идентификатор и имел этот идентификатор в области видимости вне макроса. К счастью, хотя Racket по умолчанию обеспечивает гигиену, она позволяет вам нарушать (или "сгибать") гигиену, когда вы этого хотите.
Когда вы используете #'
ака syntax
вы используете гигиенические макросы. Это означает, что ваше определение callme
виден только внутри test-d
и он не будет виден вызывающему коду. Тем не мение, datum->syntax
это один из основных механизмов, который позволяет нарушать гигиену: он "подделывает" новый фрагмент синтаксиса, который в той же области, что и другой фрагмент синтаксиса, в вашем случае stx
, который является входом для макроса. Вот почему callme2
виден снаружи test-e
определение.
Тем не менее, это тяжелый молот... слишком тяжелый, на самом деле. Ваш test-e
макрос жестоко негигиеничен, и это означает, что он может быть нарушен, если пользователь макроса связывает имя, используемое test-e
, Например, если пользователь определяет локальную переменную с именем begin
, test-e
больше не будет работать
(define-syntax (test-e stx)
(datum->syntax stx '(begin
(define (callme2 a)
(writeln a)))))
(let ([begin 42])
(test-e)
(callme2 1))
define: not allowed in an expression context
Вы можете избежать этой проблемы, будучи более консервативным в отношении того, как вы нарушаете гигиену. Действительно, в этой ситуации единственный фрагмент макроса, который мы хотим быть негигиеничным, это callme2
идентификатор, поэтому мы можем подделать этот кусок синтаксиса с помощью datum->syntax
, но использовать #'
для всего остального:
(define-syntax (test-e stx)
(with-syntax ([callme-id (datum->syntax stx 'callme2)])
#'(begin
(define (callme-id a)
(writeln a)))))
(let ([begin 42])
(test-e)
(callme2 1))
Теперь программа работает, и она негигиенична в том месте, где она должна быть.