我是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/