Написание макроса 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))

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

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

person dyouteotyi    schedule 11.04.2018    source источник


Ответы (3)


Ваш второй самый близкий. Гигиенический макрос let* можно написать с помощью _ 2_. (Если вы пишете это в виде реализации схемы, а не рэкет, вы также можете просто составить 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 ...))
person Leif Andersen    schedule 11.04.2018
comment
Спасибо за такой быстрый ответ. Я очень беспокоился об использовании рекурсии в гигиеническом макросе, и я не очень хорошо умею использовать эллипсы. Гигиеничность пусть макроса довольно проста. - person dyouteotyi; 11.04.2018
comment
Да, вы можете (и часто должны) использовать рекурсию с гигиеничным макросом. Хотя убедитесь, что есть базовый вариант, иначе он никогда не перестанет расширяться. ;) - person Leif Andersen; 11.04.2018

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 ...)))))
person user448810    schedule 11.04.2018
comment
Я не смог найти никакого решения, и я не уверен, как работают эллипсы в схеме, спасибо за этот сайт, который я обязательно найду очень полезным. - person dyouteotyi; 11.04.2018

Что касается 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 не будут работать.

person Sylwester    schedule 11.04.2018
comment
Понятно, а значит, можно любой макрос переписать в макрос гигиены (даже через это не нужно)? Мне нравится ваш последний пример определения макроса и синтаксических правил. Я хочу делать большинство макросов, используя гигиену, потому что иногда я теряюсь в квазиквотах. Мне было интересно, есть ли что-то вроде macroexpand, как в Common Lisp. - person dyouteotyi; 11.04.2018
comment
@dyouteotyi syntax-rules иногда может быть очень многословным. Racket имеет более двух различных макросистем, одна из которых использует язык ракетки и в то же время гигиенична. В моем ответе вы видите, что я использую expand-once, и он, а expand совпадает с macroexpand-1 и macroexpand. Кроме того, в DrRacket есть макрос-степпер, с помощью которого вы можете шаг за шагом выполнять каждое преобразование. Это тот, который я использую чаще всего. - person Sylwester; 11.04.2018