В чем разница между этими макросами?

У меня есть несколько вопросов о том, как работают макросы в Scheme (в частности, в Chicken Scheme), давайте рассмотрим этот пример:

(define (when-a condition . body)
  (eval `(if ,condition
     (begin ,@body)
     '())))

(define-syntax when-b
  (er-macro-transformer
    (lambda (exp rename compare)
      (let ((condition (cadr exp))
            (body (cddr exp)))
        `(if ,condition (begin ,@body) '())))))

(define-syntax when-c
  (ir-macro-transformer
    (lambda (exp inject compare)
      (let ((condition (cadr exp))
            (body (cddr exp)))
        `(if ,condition (begin ,@body) '())))))

(define-syntax when-d
  (syntax-rules ()
    ((when condition body ...)
     (if condition (begin body ...) '()))))
  1. Могу ли я считать when-a макросом? Я чувствую, что не могу строго считать это макросом, так как я не использую define-syntax, но я не могу назвать никаких практических причин, чтобы не предпочесть эту реализацию.

  2. Являются ли мои макросы гигиеничными?

  3. Есть ли разница между when-b и when-c? Поскольку я не использую ни rename, ни inject, я думаю, что нет.


person Andrea Ciceri    schedule 08.01.2018    source источник


Ответы (1)


Могу ли я считать, когда макрос? Я чувствую, что не могу строго рассматривать это как макрос, так как я не использую синтаксис определения, но я не могу сказать никаких практических причин, чтобы не предпочесть эту реализацию.

Это работает как макрос, но это не совсем то же самое, что и настоящий макрос, по следующим причинам:

  • Основное различие между настоящим макросом и вашим "макросом" на основе eval заключается в том, что ваш подход будет оценивать все его аргументы перед его вызовом. Это очень важное отличие. Например: (if #f (error "oops") '()) будет оцениваться как '(), но (when-a #f (error "oops")) вызовет ошибку.
  • Это не гигиенично. Предварительно можно было бы сделать что-то вроде (eval '(define if "not a procedure")), например, и это означало бы, что этот eval потерпит неудачу; if в выражении тела "расширения" не относится к if на сайте определения.
  • Он не расширяется во время компиляции. Это еще одна важная причина для использования макроса; компилятор расширит его, и во время выполнения не будет выполняться никаких вычислений для выполнения расширения. Сам макрос полностью испарится. Остается только расширение.

Являются ли мои макросы гигиеничными?

Только when-c и when-d, из-за гарантий, сделанных ir-macro-transformer и syntax-rules. В when-b вам придется переименовать if и begin, чтобы они ссылались на версии if и begin на сайте определения макросов.

Пример:

(let ((if #f))
  (when-b #t (print "Yeah, ok")))

== expands to ==>

(let ((if1 #f))
  (if1 #t (begin1 (print "Yeah, ok"))))

Это не удастся, потому что обе версии if (здесь аннотированные с дополнительным суффиксом 1) ссылаются на одно и то же, поэтому мы закончим вызовом #f в позиции оператора.

Напротив,

(let ((if #f))
  (when-c #t (print "Yeah, ok")))

== expands to ==>

(let ((if1 #f))
  (if2 #t (begin1 (print "Yeah, ok"))))

Который будет работать по назначению. Если вы хотите переписать when-b, чтобы сделать его более гигиеничным, сделайте это следующим образом:

(define-syntax when-b
  (er-macro-transformer
    (lambda (exp rename compare)
      (let ((condition (cadr exp))
            (body (cddr exp))
            (%if (rename 'if))
            (%begin (rename 'begin)))
        `(,%if ,condition (,%begin ,@body) '())))))

Обратите внимание на дополнительные идентификаторы с префиксом %, которые относятся к исходному значению if и begin, поскольку они были в месте определения макроса.

Есть ли разница между when-b и when-c? Поскольку я не использую ни переименование, ни инъекцию, я думаю, что нет.

Есть. Макросы неявного переименования называются так потому, что они неявно переименовывают все идентификаторы, поступающие с сайта использования, а также каждый новый идентификатор, который вы вводите в теле. Если вы вводите какие-либо идентификаторы, это отменяет это неявное переименование, что делает их негигиенично доступными для захвата вызывающим кодом.

С другой стороны, макросы явного переименования называются так потому, что вы должны явно переименовывать любые идентификаторы, чтобы предотвратить их захват вызывающим кодом.

person sjamaan    schedule 08.01.2018
comment
Спасибо, и последнее: используя Chicken, как я могу частично скомпилировать код, чтобы увидеть расширенные макросы? Есть ли способ? - person Andrea Ciceri; 08.01.2018
comment
Вы можете использовать ,x в интерпретаторе. Есть также расширенное яйцо, но оно довольно сломано ( он неправильно поддерживает макросреды, поэтому он плохо обрабатывает let-syntax и тому подобное). Наконец, очень практичный прием, которым пользуются многие программисты, заключается в расширении до списка в кавычках (просто добавьте ' перед выводом), чтобы вы могли видеть, как будет выглядеть расширение. - person sjamaan; 09.01.2018