macros - 如何使用with语法宏

标签 macros scheme racket

我是Scheme的新手,在阅读SICP时,我发现:

->我需要阅读《TheScheme编程语言4》,

-->我需要阅读 r6rs,

--->我读了“又一个方案教程”,

--->我需要阅读“使用语法大小写在Scheme中编写卫生宏”。

当阅读最后一篇时,我尝试:

(define-syntax with-syntax1 ;;;racket has a with-syntax
  (lambda (x)
    (syntax-case x ()
      ((_ ((p e0) ...) e1 e2 ...)
       (syntax (syntax-case (list e0 ...) ()
                 ((p ...) (begin e1 e2 ...))))))))

(define-syntax or1
  (lambda (x)
    (syntax-case x ()
      ((_) (syntax #f))
      ((_ e) (syntax e))
      ((_ e1 e2 e3 ...)
       (with-syntax1 ((rest (syntax (or e2 e3 ...))))
         (syntax (let ((t e1)) (if t t rest))))))))

我收到一个错误: 休息:模块中的未绑定(bind)标识符(在第一阶段,变压器环境中):休息

//-------------------------------------------------------- -------------------

当使用racket的“with-syntax”来定义另一个or时:

(define-syntax or
  (lambda (x)
    (syntax-case x ()
      ((_) (syntax #f))
      ((_ e) (syntax e))
      ((_ e1 e2 e3 ...)
       ;;;use racket's with-syntax
       (with-syntax ((rest (syntax (or e2 e3 ...))))
         (syntax (let ((t e1)) (if t t rest))))))))

将其称为(或 1 2),调用将永远不会结束。

//---第二个问题的根本原因已经找到了--------------------

我的问题是:

上面两个“或”有什么问题。

有没有我可以遵循的路线图(或书单,一一)来学习该方案/ Racket ?

我对Scheme中的“卫生宏”很感兴趣,我想学习如何编写宏,并且,我也想了解卫生宏背后的理论。

最佳答案

第一个错误是由于相位不匹配造成的。 Racket 使用阶段来告诉哪些代码需要在编译时(即宏扩展时)与运行时执行。为了在具有宏和副作用的语言中获得可预测和可重复的编译,阶段是必要的。

在您的宏中,with-syntax1 在宏转换器中使用来计算结果语法,因此 with-syntax1 的定义必须在编译时发生(相对于顶层)。这是修复程序的第一步:

(begin-for-syntax
  (define-syntax with-syntax1 (lambda (x) ___)))
(define-syntax or1 ___)

但是,如果您运行该程序,您会收到有关 lambda 在第 2 阶段未绑定(bind)的错误。这是因为 lambda 在宏转换器在编译时定义,因此它必须在编译时的编译时可用!也就是说,不只是两个阶段;可以有很多级别的“编译时间”。我们将“运行时”标记为阶段 0,“编译时”标记为阶段 1,“编译时的编译时”标记为阶段 1,依此类推。

这是对您的示例的完整修复:

(require racket/base               ;; phase 0
         (for-syntax racket/base)  ;; phase 1
         (for-meta 2 racket/base)) ;; phase 2

(begin-for-syntax                   ;; A
  (define-syntax with-syntax1       ;; B
    (lambda (x)                     ;; C
      (syntax-case x ()
        ((_ ((p e0) ...) e1 e2 ...)
         (syntax
          (syntax-case (list e0 ...) () ;; D
            ((p ...) (begin e1 e2 ...)))))))))

(define-syntax or1                  ;; E
  (lambda (x)                       ;; F
    (syntax-case x ()
      ((_) (syntax #f))
      ((_ e) (syntax e))
      ((_ e1 e2 e3 ...)
       (with-syntax1 ((rest (syntax (or e2 e3 ...)))) ;; G
         (syntax (let ((t e1)) (if t t rest))))))))

以下是有关此程序中的绑定(bind)和阶段的一些注释:

  • 在 A 行,begin-for-syntax 是第 0 阶段对特殊形式的引用,该特殊形式将其主体移高一个阶段(因此主体处于第 1 阶段)。
  • 在 B 行,define-syntax 是第 1 阶段的引用,受 (require (for-syntaxracket/base)) 约束;它导致 with-syntax1 在第 1 阶段被定义为宏,并且转换器表达式位于第 2 阶段。
  • 在 C 行上,lambda 是第 2 阶段的引用,受 (require (for-meta 2racket/base)) 约束。
  • 在 D 行,syntax-case 出现在语法模板内,这不算在宏中使用它。它还不是一个引用,但当使用 with-syntax 宏时,它会生成一个引用。使用with-syntax1 宏在第 1 阶段定义,因此必须在第 1 阶段使用,这意味着对 syntax 的引用-case 它产生的必须在阶段 1 绑定(bind)。并且它是由第二个 require 绑定(bind)的。
  • 对于家庭作业,标记行 E、F 和 G :)

还有其他方法可以构建这样的程序。一种是将 with-syntax1 放在另一个模块中并要求它 for-syntax:

(模块 with-syntax1-mod _) (模块 or1-mod _ (require (for-syntax 'with-syntax-mod)) ___)

分阶段施加的约束保持不变,但现在在标记阶段时必须小心:“相对于 with-syntax1-mod 的阶段 1”与“相对于 or1-mod 的阶段 2”是同一阶段因为它是 for-syntax 所必需的(即,在阶段 +1)。

我建议阅读macros section Racket Guide的,特别是后面的阶段部分。 Fear of Macros也有助于揭开有关宏的一些问题的神秘面纱。

关于macros - 如何使用with语法宏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42687649/

相关文章:

haskell - 如何在我的 LISP 中实现宏系统

scheme - 如何在 MIT Scheme 中获取我的函数定义?

scheme - 将元素插入列表列表中子列表的开头

scheme - 知道如何在 dr racket 中交错两个列表吗?

c++ - 重新定义宏时会发生什么?

rust - 如何忽略 `#[derive(Debug)]` 的通用参数?

c# - 创建内联函数和宏

scheme - 反向列表 - 方案

function - 重新定义一个函数,在Scheme中用来定义这个函数

scheme - 如何在 Racket 中使用 check-expect