我正在使用一个流畅的界面(对于流畅的一个非常奇怪的定义(,以便我可以更好地学习球拍宏,所以我模拟了这段代码来玩。
#lang racket
(require racket/syntax)
(struct Person (name location))
(define-syntax what
(syntax-rules (is)
((what is x thing) (what is x's thing))
((what is x quote-s thing)
(let* ((x-str (~a (quote x)))
(x-id (format-id #'x-id "~a" x-str))
(thing-str (~a (quote thing)))
(thing-proper-str (cadr (regexp-match #rx"(.*)\?" thing-str)))
(thing-id (format-id #'thing-id "Person-~a" thing-proper-str)))
((eval thing-id) (eval (syntax-e x-id)))))))
(define John (Person "John Luser" "Here"))
(what is John's name?)
运行此操作会导致John: unbound identifier;
also, no #%top syntax transformer is bound in: John
。如果我不得不猜测,我会说我对标识符和字符串所做的所有放置都剥夺了符号 John 的绑定,或者我在错误的环境中逃避,但我不知道如何解决其中任何一个。
只是为了比较:如果您将大部分工作推到编译时阶段,您的宏如下所示:
#lang racket
(require (for-syntax racket/format
racket/syntax))
(struct Person (name location))
(define-syntax (what stx)
(syntax-case stx (is)
((what is x thing)
#'(what is x's thing))
((what is x quote-s thing)
(let* ((thing-str (~a (syntax-e #'thing)))
(thing-proper-str (cadr (regexp-match #rx"(.*)\?" thing-str)))
(thing-id (format-id stx "Person-~a" thing-proper-str)))
#`(#,thing-id x)))))
(define John (Person "John Luser" "Here"))
(what is John's name?)
需要注意的一件事是,我们正在做(require (for-syntax ...))
,因为我们在编译时使用这些库,在程序源代码的预处理期间。
另外,请注意,它稍微简单一些,因为由于我们并没有真正对正在操作的x
进行任何修改,因此我们可以将该语法保留在输出的语法中。
如果有帮助,请考虑一下您在其他语言中的体验。 如果您熟悉 Java,请考虑在程序中编写表达式:"hello " + "world"
某处,然后编译程序时会发生什么情况。 您是否希望 Java 编译器生成字节码,该字节码在执行时构造"hello "
字符串、"world"
字符串以及两者之间的字符串连接? 大多数人会说"不,编译器应该将其重写为仅"hello world"
文字作为编译的一部分"。
这是预处理。 当你在Racket中编写宏时,你正在教Racket更多这些预处理规则。
在这里,让我们尝试以下操作:我们将添加一个知道如何连接字符串的 cat
命令。 也就是说,让我们将该串联示例变为现实。
这是版本一:
#lang racket
(define (cat x y)
(string-append x y))
(define msg-head "hello ")
(define msg-tail " world")
(cat msg-head msg-tail)
(cat "hiya " "world")
现在,我们知道,作为一个常规函数,这里cat
的两种用法最终都减少为对string-append
函数的调用。 编译器不够聪明,不知道如何触摸cat
的第二次使用。
我们想做同样类型的编译器重写规则:如果编译器预先看到源代码正在尝试cat
两个字符串文字,为什么不在编译时这样做,以便发出的字节码更好?
好吧,让我们这样做。 版本二如下:
#lang racket
(define-syntax (cat stx)
(syntax-case stx ()
[(_ x y)
;; At this point, the following code is being run by the _compiler_.
(cond
;; If in the source code we're transforming, both x and y are
;; strings, we can do the concatenation at _compile time_.
[(and (string? (syntax-e #'x))
(string? (syntax-e #'y)))
(let ([xy
(string-append (syntax-e #'x) (syntax-e #'y))])
;; Once we have this string, we still need to emit it back as the
;; result of the rewrite. We want to produce a piece of _syntax_
;; in place of the original stx.
(datum->syntax stx xy))]
[else
;; Otherwise, we want to produce a piece of syntax that looks like a
;; plain function call to string-append.
(datum->syntax stx (list 'string-append #'x #'y))])]))
(define msg-head "hello ")
(define msg-tail " world")
(cat msg-head msg-tail)
(cat "hiya " "world")
现在这种转变的问题在于观察:如果我们做对了,没有人能够分辨出其中的区别! :P 否则,这将是一个破碎的转换。 因此,要更轻松地查看差异,请按 DrRacket 工具栏上的宏步进器按钮:它会调用编译器,但会显示程序在转换为字节码之前发生的转换。
哇,我觉得自己很傻吗!发布此内容几分钟后修复了它,将((eval thing-id) (eval (syntax-e x-id)))
更改为仅((eval thing-id) (eval x-id))