在 Scheme 中编写 myletstar 宏(卫生)

Writing myletstar macro (hygiene) in Scheme

我正在尝试重写 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* -> 错误:lambda:不是标识符:(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))

编辑: 问题已回答,通过编辑 Lief Andersen 代码

添加了不使用 let 的解决方案
(define-syntax let*
  (syntax-rules ()
    ((let*2 ([x1 e1][x2 e2] ...)body ...)
     ((lambda  (x1)
        (let* ([x2 e2] ...)
           body ...))
         e1))))

你的第二个是最接近的。卫生 let* 宏可以用 define-syntax-rule 编写。 (如果你在 scheme 而不是 racket 的实现中写这个,你也可以只组合 define-syntaxsyntax-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*following definition

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

syntax-rules 将计算为 (2 1)define-macro 将不起作用。