0

我正在玩一个流利的界面(对于流利的一个非常奇怪的定义),以便我可以更好地了解球拍宏,所以我模拟了这段代码来玩。

#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 的绑定符号,或者我在错误的环境中进行评估,但我不知道如何修复那些。

4

2 回答 2

2

Just to compare: here's what your macro looks like if you push most of the work off to the compile-time phase:

#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?)

One thing to note is that we're doing (require (for-syntax ...)) because we're using those libraries at compile-time, during the pre-processing of the program's source code.

Also, note that it's a little simpler because since we're not really doing any munging of the x being acted on, we can just keep that piece of syntax preserved in the outputted syntax.


If it helps, think about your experience in other languages. If you're familiar with Java, consider what happens if you write the expression: "hello " + "world" somewhere in your program, and then compile the program. Do you expect the Java compiler to produce bytecode that, when executed, constructs a "hello " string, a "world" string, and the string concatenation between the two? Most would say "No, the compiler should rewrite that to just the "hello world" literal as a part of compilation".

This is preprocessing. When you write macros in Racket, you are teaching Racket more of these preprocessing rules.

Here, let us try the following: we'll add a cat command that knows how to concatenate strings. That is, let's bring that concatenation example to life.

Here's version one:

#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")

Now, we know that as a regular function, both uses of cat here reduce eventually down to calls to the string-append function. The compiler isn't smart enough to know how to touch the second use of cat.

But what if we wanted to do the same sort of compiler rewrite rule: if the compiler sees up front that the source code is trying to cat two string literals, why not do that at compile-time, so that the emitted bytecode is better?

Well, let's do that. Version two follows:

#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")

Now the problem with this kind of transformations is one of observation: if we did this right, no one should be able to tell the difference! :P Otherwise, it would be a broken transformation. So to see the difference more easily, press the Macro Stepper button on the DrRacket toolbar: it invokes the compiler, but shows you what transformations happen to your program right before it gets turned into bytecode.

于 2013-10-04T05:08:31.137 回答
1

哇,我是不是觉得很傻!发布后几分钟通过更改((eval thing-id) (eval (syntax-e x-id)))为仅修复它((eval thing-id) (eval x-id))

于 2013-10-03T00:21:17.773 回答