Написание myletstar макроса (гигиена) в схеме

Я пытаюсь переписать let* макрос гигиены, у меня он есть как обычный макрос, и я хотел бы иметь его как макрос гигиены, если это возможно. У меня не слишком много опыта с этими типами макросов. Так что я был бы очень признателен за помощь. Также мое другое представление о работе let* макрос не работает, с той же ошибкой, что и гигиенический макрос.

За работой let*

(define-macro let*1
(lambda (assgn . body)
(let ((loop (gensym))
      (lst (gensym)))
  (let loop ((lst assgn))
     (if (null? lst)
         `(begin ,@body)
         `((lambda (,(caar lst))
             ,(loop (cdr lst)))
           ,(cadar lst)))))))

Не работает гигиена let* -> ошибка: лямбда: не идентификатор в: (caar lst)

(define-syntax let*2
(syntax-rules ()
((let*2 (set ...) body ...)
 (let loop ((lst '(set ...)))
   (if (null? lst)
       body ...
       ((lambda ((caar lst))
          (loop (cdr lst)))
        (cadar lst) 1))))))

Не работает let*но также с той же ошибкой, что и вторая.

(define-macro let*3
(lambda (assgn . body)
(let ((loop (gensym))
      (lst (gensym)))
  `(let ,loop ((,lst assgn))
     (if (null? ,lst)
         (begin ,@body)
         ((lambda ((caar ,lst))
             (,loop (cdr ,lst)))
           (cadar ,lst)))))))

Извиняюсь за небольшой запутанный вопрос, я застрял с этой проблемой в течение некоторого времени, и кофеин больше не полезен.

Некоторые тесты (я выбрал имена символов для проверки захвата символов (я знаю, что мне не пришлось)):

(let*1 ((body 10)
   (lst (+ body 1)))
  (list body lst))

(let*2 ((body 10)
    (lst (+ body 1)))
  (list body lst))

(let*3 ((body 10)
    (lst (+ body 1)))
  (list body lst))

Изменить: Ответ на вопрос, добавлено решение без использования пусть путем редактирования кода Leif Andersen

(define-syntax let*
  (syntax-rules ()
    ((let*2 ([x1 e1][x2 e2] ...)body ...)
     ((lambda  (x1)
        (let* ([x2 e2] ...)
           body ...))
         e1))))

3 ответа

Решение

Ваш второй самый близкий. Гигиенический let* макрос может быть записан с define-syntax-rule, (Если вы пишете это в реализации схемы, а не ракетки, вы также можете просто составить define-syntax а также syntax-rule для того же эффекта.)

(define-syntax-rule (let* ([x1 e1]
                           [x2 e2] ...)
                      body ...)
  (let ([x1 e1])
    (let* ([x2 e2] ...)
      body ...))

И если вы чувствуете себя по-настоящему педантичным, вы можете сделать гигиенический let макрос:

(define-syntax-rule (let ([x e] ...)
                      body ...)
  ((lambda (x ...) body ...) e ...))

R6RS, Приложение B дает следующее определение let*:

(define-syntax let*
  (syntax-rules ()
    ((let* () body1 body2 ...)
     (let () body1 body2 ...))
    ((let* ((name1 expr1) (name2 expr2) ...)
       body1 body2 ...)
     (let ((name1 expr1))
       (let* ((name2 expr2) ...)
         body1 body2 ...)))))

Когда дело доходит до let* Я не думаю, что гигиена является проблемой. Имена в let Вы хотите выставить, и вы не вводите никаких других привязок.

(require compatibility/defmacro)
(define-macro let*1
  (lambda (assgn . body)
    (let loop ((lst assgn))
      (if (null? lst)
          `(begin ,@body)
          `((lambda (,(caar lst))
              ,(loop (cdr lst)))
            ,(cadar lst))))))

(expand-once 
 #'(let*1 
     ((a 1) (b 2) (c 3)) 
       (list a b c)))

; ==> #'((lambda (a)
;          ((lambda (b)
;            ((lambda (c)
;               (begin
;                 (list a b c)))
;             3))
;          2))
;       1)

Кажется, хорошо для меня. Теперь вы можете сделать то же самое, используя syntax-rules, Этот метод не использует язык схем и поэтому вы пытаетесь сделать (caar lst) Предполагается, что вы хотите, чтобы в результате расширения.

(expand-once #'(let*2 ((a 1) (b 2) (c 3)) (list a b c)))
; ==> #'(let loop ((lst '((a 1) (b 2) (c 3))))
;         (if (null? lst)
;             (list a b c)
;             ((lambda ((caar lst))
;                (loop (cdr lst)))
;              (cadar lst)
;              1)))

Обратите внимание, как ваш код фактически копируется дословно в полученное расширение. Это потому, что все, что не деструктурировано в шаблоне, предполагается в лексической области самого синтаксиса. Вот как это сделать:

(define-syntax let*2
  (syntax-rules ()
    ;; handle stop condition (no more bindings)
    ((let*2 ()  body ...) 
     (begin body ...))
    ;; handle one expansion
    ((let*2 ((name expr) more-bindings ...) body ...)
     (let ((name expr))
       (let*2 (more-bindings ...)
         body ...))))) 
(expand-once 
 #'(let*2 
     ((a 1) (b 2) (c 3)) 
       (list a b c)))

; ==> #'((lambda (a) 
;          (let*2 ((b 2) (c 3)) 
;            (list a b c))) 1)

expand-once только один уровень, но с помощью макроэкспандера вы увидите, что он продолжается до тех пор, пока все части не будут расширены.

Итак, когда вам нужна гигиена? Это когда вы вводите привязки в ваш макрос. Стандартный пример обычно swap! это меняет значения двух привязок:

(define-syntax swap!
  (syntax-rules ()
    ((swap! var1 var2)
     (let ((tmp var1))
       (set! var1 var2)
       (set! var2 tmp)))))

С syntax-rules хотя каждая привязка, а не шаблон, должна находиться в лексической области действия макроса, и все это не является новой привязкой, которая создается новой при каждом расширении. Таким образом, я могу безопасно сделать это:

(let ((tmp 1) (another 2))
  (swap! tmp another)
  (list tmp another))
; ==> (2 1)

Со старым стилем вам нужно это:

(define-macro swap!
  (lambda (var1 var2)
    (let ((tmp (gensym "tmp")))
      `(let ((,tmp ,var1))
         (set! ,var1 ,var2)
         (set! ,var2 ,tmp)))))

Это работает для примера выше, но если бы я должен был сделать:

(let ((set! 1) (let 2))
  (swap! set! let)
  (list set! let))

Синтаксические правила, которые нужно оценить (2 1) в то время как define-macro никто не будет работать.

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