Scheme 球拍宏观扩张中的评价形式

Scheme 球拍宏观扩张中的评价形式,scheme,lisp,racket,Scheme,Lisp,Racket,这是一个常见的Lisp宏和测试函数 (defmacro test (body) `(let ,(mapcar #'(lambda (s) `(,s ,(char-code (char-downcase (char (symbol-name s) 0))))) '(a b)) ,body)) (test (+ a b)) 扩展到 (let ((a 97) (b 98)) (+ a b)) 评估时给出195 在球拍里试着这么

这是一个常见的Lisp宏和测试函数

(defmacro test (body)
  `(let ,(mapcar #'(lambda (s)
             `(,s ,(char-code (char-downcase (char (symbol-name s) 0)))))
             '(a b))
     ,body))

(test (+ a b))
扩展到

(let ((a 97) (b 98))
  (+ a b))
评估时给出195

在球拍里试着这么做

(define-syntax (test stx)
  (syntax-case stx ()
    [(_ body)
     #`(let #,(map (lambda (x)
                     (list x
                           (char->integer (car (string->list (symbol->string x))))))
                   '(a b))
         body)]))

(test (+ a b))
运行宏扩展器时,宏窗体将扩展为:

(let ((a 97) (b 98)) (+ a b))))
这正是我想要的

但它在以下方面失败了:

a: unbound identifier in context..
禁用宏隐藏会生成一个以以下内容结尾的表单:

(#%app:35
 call-with-values:35
 (lambda:35 ()
  (let-values:36 (((a:37) (quote 97)) ((b:37) (quote 98)))
   (#%app:38 + (#%top . a) b)))
  (print-values:35)))
我不明白为什么我漂亮的扩展
(let((a97)(b98))(+ab))
不起作用,我对(#%top.a)。。。我想知道它是否在试图找到一个叫做“a”的函数? 当我将扩展的表单复制到REPL中时,它会工作


我很感激你的帮助

球拍有卫生宏。考虑:

(define-syntax-rule (or a b)
  (let ([a-val a]) 
    (if a-val a-val b)))
然后:

将大致扩展到:

(let ([a-val 1])
  (let ([a-val2 #f]) 
    (if a-val2 a-val2 a-val)))
其计算结果为
1
。如果宏不卫生,则会导致被认为不正确的
#f

请注意,
a-val
自动重命名为
a-val2
,以避免冲突。你的案子也是这样

解决本例问题的一种方法是为生成的标识符提供正确的上下文,以便宏扩展器理解它们应该引用相同的变量

(define-syntax (test stx)
  (syntax-case stx ()
    [(_ body)
     #`(let #,(map (lambda (x)
                     (list (datum->syntax stx x) ; <-- change here
                           (char->integer (car (string->list (symbol->string x))))))
                   '(a b))
         body)]))

(test (+ a b))
(定义语法(测试stx)
(语法大小写stx()
[(uu)正文)
#`(让#,,(图(λ(x))
(列表(基准->语法stx x);整数(car(字符串->列表(符号->字符串x‘‘‘‘‘)’)
"(a)(b)
(正文))
(测试(+AB))
作为对应项(这是正确的答案),我认为有必要思考一下为什么您的
测试
宏在CL中有问题,为什么做类似事情的宏完全有问题

给定您的
测试
宏,想象一些用户正在查看此代码:

(let ((a 1) (b 2))
  (test (+ a b)))
嗯,我不知道你的情况,但我希望发生的是
a
b
内部
测试
是我刚刚绑定的
a
b
。当然,事实并非如此

嗯,也许
test
的文档非常详细地描述了它绑定了两个变量,这就是我应该期望的。当然,也有宏可以做到这一点,而且在哪里是好的:

(defmacro awhen (test &body forms)
  `(let ((it ,test))
     (when ,it ,@forms)))
现在:

(awhen (find-exploder thing)
  (explode it))
这一切都很好,因为
awhen
的文档会说它将
it
绑定到其主体中的测试结果

但现在请考虑这个代码>或代码>宏:从另一个答案中窃取:

(defmacro vel (a b)
  `(let ((a-val ,a))
     (if a-val a-val ,b)))
这是一场灾难。它“起作用”,但它根本不起作用:

> (let ((a-val 3))
    (vel nil a-val))
nil
现在这不仅仅是在你的
测试
宏的方式上令人惊讶:它是错误的

相反,您必须在CL中这样编写
vel

(defmacro vel (a b)
  (let ((a-val-name (make-symbol "A-VAL")))
    `(let ((,a-val-name ,a))
       (if ,a-val-name ,a-val-name ,b))))
(当然,您可以使用
gensym
而不是
makesymbol
,我想大多数人都会这么做。)

现在呢

> (let ((a-val 3))
  (vel nil a-val))
3
> (with-char-codes (a b)
    (+ a b))
195
如你所料

这一切都是因为CL宏系统不卫生——它依赖于您来确保名称等内容不会冲突。在CL中,您必须稍微费劲地编写宏,这些宏在许多情况下都是正确的。另一方面,Racket宏系统是卫生的:默认情况下,它将确保名称(和其他内容)不会冲突。在Racket(和Scheme)中,您必须不遗余力地编写宏,这些宏要么不正确,要么做一些稍微出乎意料的事情,比如引入从代码中可见的绑定来使用宏

请注意,我并没有表达对这两种宏方法的偏好:我花了大部分时间编写CL,我对它的宏系统非常满意。最近我写了更多的《球拍》,我对它的宏观系统也很满意,尽管我发现它更难理解

最后,这里是一个在使用中不太令人惊讶的宏变体(此代码中几乎所有的杂音都是健全性检查,
语法分析
以两个
#:失败时
子句的形式支持):

现在呢

> (let ((a-val 3))
  (vel nil a-val))
3
> (with-char-codes (a b)
    (+ a b))
195