В чем разница между датум-> синтаксис и синтаксис #'в теле определения-синтаксиса?

Тестовый код:

(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))

Теперь программа работает, и она негигиенична в том месте, где она должна быть.

Другие вопросы по тегам