схема — цитирование и вспомогательный синтаксис

#scheme #racket

#схема #ракетка

Вопрос:

По какой-то причине следующий макрос не сможет работать с кавычками.

 (define-syntax expand
    (lambda (stx)
      (syntax-case stx (break)
        [(_ k () (ys ...)) (begin (println (syntax->datum #'(ys ...))) #'(ys ...))]
        [(_ k ((break) xs ...) (ys ...)) #'(expand k (xs ...) (ys ... (k (void))))]
        [(_ k ((es ...) xs ...) (ys ...)) #'(expand k (xs ...) (ys ... (expand k (es ...) ())))]
        [(_ k (a xs ...) (ys ...)) #'(expand k (xs ...) (ys ... a))])))

(define-syntax loop
    (syntax-rules ()
      [(_ e es ...)
       (call/cc (lambda (k)
                  (let l ()
                    (expand k (begin e es ...) ())
                    (l))))]))

(loop (list 1 (break)))
;; => works fine 

(loop (quasiquote (1 (unquote (break)))))
;; => break: unbound identifier in module in: break
  

Я немного удивлен, увидев, почему второй случай завершается неудачей.

И следующая отладочная информация печатается для обоих случаев.

 ;; case 1
'(begin (expand k (list 1 (break)) ()))
'(list 1 (k (void)))

;; case 2
'(begin (expand k `(1 ,(break)) ()))
'`(expand k (1 ,(break)) ()) 
  

Пожалуйста, обратите внимание, что в выводе для случая 2 после quasiquote расширения остальное (1 ,(break)) почему-то не расширяется.

Не уверен, почему это произойдет.

Спасибо

Комментарии:

1.вывод для случая 2 после quasiquote расширения я не вижу его в вашем вопросе. Последняя строка '`(expand k (1 ,(break)) ()) представляет состояние перед quasiquote расширением.

Ответ №1:

Проблема в том, что расширитель макросов не расширяет вызовы макросов, которые отображаются под quote или quasiquote . Например:

 (define-syntax-rule (pipe) "|")

> (quote (pipe))
'(pipe)                ; not "|"
> (quasiquote (pipe))
'(pipe)                ; not "|"
  

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

В общем, переведите код следующим образом:

 (define-syntax expand
  (lambda (stx)
    (syntax-case stx literals
      cases
      [pattern #'(.... (expand stuff) ...)]
      cases)))
  

В код, подобный этому:

 (begin-for-syntax
  (define (expand stx)
    (syntax-case stx literals
      cases
      [pattern #`(.... #,(expand stuff) ...)]
      cases)))
  

В вашем конкретном случае вы, вероятно, хотите expand быть функцией с тремя аргументами, которая выполняется и полностью повторяется во время компиляции.

 (begin-for-syntax
  (define (expand k xs ys)
    (with-syntax ([(ys ...) ys])
      (syntax-case xs (break)
        [()                (begin (println (syntax->datum #'(ys ...))) #'(ys ...))]
        [((break) xs ...)  (expand k #'(xs ...) #'(ys ... (k (void))))]
        [((es ...) xs ...) (expand k #'(xs ...) #`(ys ... #,(expand k #'(es ...) #'())))]
        [(a xs ...)        (expand k #'(xs ...) #'(ys ... a))]))))
  

Затем вы можете вызвать эту функцию времени компиляции в реализации loop макроса:

 (define-syntax loop
  (lambda (stx)
    (syntax-case stx ()
      [(_ e es ...)
       #`(call/cc (lambda (k)
                    (let l ()
                      #,(expand #'k #'(begin e es ...) #'())
                      (l))))])))
  

Однако это не лучший способ выполнить макрос с циклическим выполнением.

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

 (define-syntax-parameter break
  (lambda (stx) (raise-syntax-error #f "cannot be used outside of loop" stx)))

(define-syntax loop
  (syntax-rules ()
    [(_ e es ...)
     (call/cc (lambda (k)
                (define (break-function) (k (void)))
                (syntax-parameterize ([break (make-rename-transformer #'break-function)])
                  (let l ()
                    (begin e es ...)
                    (l)))))]))
  

Фактически, loop макрос, подобный этому, является одним из примеров, используемых в статье, поддерживая его в чистоте с помощью параметров синтаксиса раздел 4, called forever , где он вызывает параметр синтаксиса прерывания abort .