对于宏的恐惧

这只是我阅读Fear of Macros记下的笔记.

第1章 前言

第2章 我们的作战计划

第3章 变换!

第3.1节 什么是一个句法转换器?

简而言之, 一个句法转换器接受句法而返回句法. 换言之, 其对于句法进行变换.

以下是一个转换器的例子, 其直接忽略了输入的句法.

> (define-syntax foo
    (lambda (stx)
      (syntax "I am foo")))
> (foo)
"I am foo"
使用define-syntax创建了转换器的绑定.

恰如define, define-syntax也有类似的句法糖.

> (define-syntax (also-foo stx)
    (syntax "I am also foo"))
> (also-foo)
"I am also foo"

恰如'quote的简记法, #'syntax的简记法.

> (define-syntax (quoted-foo stx)
    #'"I am also foo, using #' instead of syntax")
> (quoted-foo)
"I am also foo, using #' instead of syntax"

当然, 返回的句法不仅限于字符串字面量.

> (define-syntax (say-hi stx)
    #'(displayln "hi"))
> (say-hi)
hi

第3.2节 输入的是什么?

之前的例子只是直接忽略了输入的句法, 但一般情况下我们总是想要输入的句法转换为别的什么东西. 首先, 让我们来仔细观察一下输入的究竟是什么.

> (define-syntax (show-me stx)
    (print stx)
    #'(void))
> (show-me '(+ 1 2))
#<syntax:eval:10:0 (show-me (quote (+ 1 2)))>
从中可以看出, 转换器接受的是一个句法对象 (syntax object).

一个句法对象除了字面, 还包含了诸多有趣的信息, 例如其位置还有关于词法作用域的东西. {译注: 因此, 读者会发现这里的(交互所呈现的)句法对象的信息, 大概和自己试验时不太一样.}

存在着各种各样可以访问句法对象的函数. 首先, 让我们定义一个句法.

> (define stx #'(if x (list "true") #f))
> stx
#<syntax:eval:11:0 (if x (list "true") #f)>
然后, 以下是一些用于获取源信息的函数.
> (syntax-source stx)
'eval
> (syntax-line stx)
11
> (syntax-column stx)
0
更有趣的是句法字面本身, 我们可以用syntax->datum将其转换为一个S-expression.
> (syntax->datum stx)
'(if x (list "true") #f)
与之相对的是, syntax-e只往下走一层.
> (syntax-e stx)
'(#<syntax:eval:11:0 if> #<syntax:eval:11:0 x> #<syntax:eval:11:0 (list "true")> #<syntax:eval:11:0 #f>)
还有一个函数叫做syntax->list, 某些时候和syntax-e表现类似, 但其实相当不同.
> (syntax->list stx)
'(#<syntax:eval:11:0 if> #<syntax:eval:11:0 x> #<syntax:eval:11:0 (list "true")> #<syntax:eval:11:0 #f>)

第3.3节 实际地对于输入进行变换

现在让我们写一个转换器函数, 其将输入的句法颠倒.

> (define-syntax (reverse-me stx)
    (datum->syntax stx (reverse (cdr (syntax->datum stx)))))
> (reverse-me "backwards" "am" "i" values)
"i"
"am"
"backwards"
datum->syntax的第一个参数包含了我们想要与输出的句法对象关联的词法上下文信息. 如果其被设置为#f, 那么就是没有信息.

第3.4节 编译时和运行时的对比

通常的Racket代码运行在运行时, 这是显而易见的. 但是, 转换器被Racket调用是parse, expand, 编译程序这一过程的组成部分. 换言之, 句法转换器函数在编译时被求值. 当然, 也有人会说句法阶段运行时阶段, 只不过是相同概念的不同说法而已.

先让我们回顾一下为什么要有宏的存在. 一个经典的例子可能是Racket的if形式.

(if <condition> <true-expression> <false-expression>)
如果我们将if实现为一个函数, 那么所有的参数都会在提供给函数之前被求值.
> (define (our-if condition true-expr false-expr)
    (cond [condition true-expr]
          [else false-expr]))
> (our-if #t
          "true"
          "false")
"true"
似乎这能够成立, 然而请看以下交互.
> (define (display-and-return x)
    (displayln x)
    x)
> (our-if #t
          (display-and-return "true")
          (display-and-return "false"))
true
false
"true"
这暗示我们if并不可能是一个平然的函数. 然而, 句法转换器可以帮助我们完成, 因为其会在编译时对于句法进行重写, 但直到运行时并不会实际进行求值.
> (define-syntax (our-if-v2 stx)
    (define xs (syntax->list stx))
    (datum->syntax stx `(cond [,(cadr xs) ,(caddr xs)]
                              [else ,(cadddr xs)])))
> (our-if-v2 #t
             (display-and-return "true")
             (display-and-return "false"))
true
"true"
> (our-if-v2 #f
             (display-and-return "true")
             (display-and-return "false"))
false
"false"

这给出了正确的答案, 但它是如何运作的呢? 让我们抽出转换器本身, 看看它到底做了什么. 首先, 让我们从一个输入句法作为例子开始.

> (define stx #'(our-if-v2 #t "true" "false"))
> (displayln stx)
#<syntax:eval:32:0 (our-if-v2 #t "true" "false")>
1. 我们取原始的句法, 使用syntax->list以将其转换为一个句法对象的列表.
> (define xs (syntax->list stx))
> (displayln xs)
(#<syntax:eval:32:0 our-if-v2> #<syntax:eval:32:0 #t> #<syntax:eval:32:0 "true"> #<syntax:eval:32:0 "false">)
2. 为了将其转换为cond形式, 我们需要从列表中取出我们所感兴趣的三个部分, 通过使用cadr, caddr, cadddr, 之后的安排则是顺理成章的.
`(cond [,(cadr xs) ,(caddr xs)]
       [else ,(cadddr xs)])
3. 最后, 我们使用datum->syntax以将其转换回为一个句法对象.
> (datum->syntax stx `(cond [,(cadr xs) ,(caddr xs)]
                            [else ,(cadddr xs)]))
#<syntax (cond (#t "true") (else "false"))>
我们大概已经明白了其工作的流程, 然而使用这些函数来解构列表并不是很清晰自然的事情, 而且也很容易出错. 因此, 我们想到可以使用Racket提供的模式匹配机制match.

我们想着与其写成

> (define-syntax (our-if-v2 stx)
    (define xs (syntax->list stx))
    (datum->syntax stx `(cond [,(cadr xs) ,(caddr xs)]
                              [else ,(cadddr xs)])))
不如写成
> (define-syntax (our-if-using-match stx)
    (match (syntax->list stx)
      [(list name condition true-expr false-expr)
       (datum->syntax stx `(cond [,condition ,true-expr]
                                 [else ,false-expr]))]))
然后, 我们会遇到
> (our-if-using-match #t "true" "false")
match: undefined;
 cannot reference an identifier before its definition
  in module: 'program
其在抱怨match并没有被定义.

我们的转换器函数在编译时工作, 然而编译时能用的只是racket/base, 并非完整的Racket语言. 为了使用超出racket/base的东西, 我们需要requirefor-syntax形式.

> (require (for-syntax racket/match))
> (define-syntax (our-if-using-match-v2 stx)
    (match (syntax->list stx)
      [(list _ condition true-expr false-expr)
       (datum->syntax stx `(cond [,condition ,true-expr]
                                 [else ,false-expr]))]))
> (our-if-using-match-v2 #t "true" "false")
"true"

第3.5节 begin-for-syntax

之前我们已经用了for-syntaxrequire需要在编译时用到的racket/match模块. 不过, 显然有时我们也需要自己定义宏所需的辅助函数. 通常的define无法解决问题, 因为正如你所知的, 其出现于运行时而非编译时. 然而, 我们可以使用begin-for-syntax.

(begin-for-syntax
 (define (my-helper-function ....)
   ....))
(define-syntax (macro-using-my-helper-function stx)
  (my-helper-function ....)
  ....)
其实也可以使用define-for-syntax.
(define-for-syntax (my-helper-function ....)
  ....)
(define-syntax (macro-using-my-helper-function stx)
  (my-helper-function ....)
  ....)

第4章 模式匹配: syntax-casesyntax-rules

绝大部分有用的句法转换器都是对于输入句法的组成部分的重新排列, 因此我们应该使用模式匹配. 实际上, Racket本来就有基于模式匹配的宏系统, 例如syntax-rulessyntax-case. {译注: Schemer都知道, 不只是Racket提供这两个, 其他许多Scheme实现也会提供这两个. 另外, 原文说Racket的define-syntax-rule是基于syntax-case的, 这种说法有点问题, 因为实际上它是基于syntax-rules的.}

基于syntax-case, 我们之前的例子

(require (for-syntax racket/match))
(define-syntax (our-if-using-match-v2 stx)
  (match (syntax->list stx)
    [(list _ condition true-expr false-expr)
     (datum->syntax stx `(cond [,condition ,true-expr]
                               [else ,false-expr]))]))
可以写成
> (define-syntax (our-if-using-syntax-case stx)
    (syntax-case stx ()
      [(_ condition true-expr false-expr)
       #'(cond [condition true-expr]
               [else false-expr])]))
> (our-if-using-syntax-case #t "true" "false")
"true"
这看起来其实和之前的也相当类似, 但是免去了使用datum->syntax还有准引用. 当然, 这种情况下使用define-syntax-rule也差不多, 甚至还更简单一点.
> (define-syntax-rule (our-if-using-syntax-rule condition true-expr false-expr)
    (cond [condition true-expr]
          [else false-expr]))
> (our-if-using-syntax-rule #t "true" "false")
"true"

第4.1节 模式变量vs.模板——fight!

Racket的struct可以完成一种有趣的事情, 即例如

(struct foo (field1 field2))
那么其会生成一些新的名字, 包括foo-field1, foo-field2, foo?.

让我们也用宏做点类似的事情. 比如说, 我们想要将句法(hyphen-define a b (args) body)转换为(define (a-b args) body).

以下是一次错误的尝试.

> (define-syntax (hyphen-define/wrong1 stx)
    (syntax-case stx ()
      [(_ a b (args ...) body0 body ...)
       (let ([name (string->symbol (format "~a-~a" a b))])
         #'(define (name args ...)
             body0 body ...))]))
eval:47:0: a: pattern variable cannot be used outside of a
template
  in: a
当然, 现在读者还无法理解错误信息的含义. 不过, 我要说明所谓的模板 (template) 指的是诸如#'(define (name args ...) body0 body ...)这样的部分. 那么, 看起来像是我们在这let绑定里不能使用a (或者b).

实际上, syntax-case里你想用多少模板就可以有多少. 虽然我们不能直接使用模式变量, 但是我们可以将其置于#'之中. 也就是说, 我们或许可以尝试一下如下定义.

> (define-syntax (hyphen-define/wrong1.1 stx)
    (syntax-case stx ()
      [(_ a b (args ...) body0 body ...)
       (let ([name (string->symbol (format "~a-~a" #'a #'b))])
         #'(define (name args ...)
             body0 body ...))]))
> (hyphen-define/wrong1.1 foo bar () #t)
> (foo-bar)
foo-bar: undefined;
 cannot reference an identifier before its definition
  in module: 'program
如你所见, 虽然定义时没有出错, 但是仍然并不符合我们的预期.

实际上, 如果你使用宏步进器, 就会发现

(hyphen-define/wrong1.1 foo bar () #t)
会被扩展为
(define (name) #t)
而非我们预期的
(define (foo-bar) #t)
我们的模板使用了符号name, 但是我们期望其能够使用name的值.

回想一下, 有什么出现在模板里的变量是使用其值的呢? 显然, 模式变量是我们已知的答案. 或许我们可以想象一种变通的解决方案, 也就是使用两次syntax-case, 其中一次用于创建模式变量name.

> (define-syntax (hyphen-define/wrong1.2 stx)
    (syntax-case stx ()
      [(_ a b (args ...) body0 body ...)
       (syntax-case (datum->syntax #'a
                                   (string->symbol (format "~a-~a" #'a #'b)))
                    ()
         [name #'(define (name args ...)
                   body0 body ...)])]))
> (hyphen-define/wrong1.2 foo bar () #t)
> (foo-bar)
foo-bar: undefined;
 cannot reference an identifier before its definition
  in module: 'program
这个定义看起来有点奇怪, 虽然看起来也很合理. 但是, 仍然并不正确.

或许我们应该继续使用宏步进器看看我们的问题究竟出在了哪里. 实际上, 我们的

(hyphen-define/wrong1.2 foo bar () #t)
被转换为了
(define (|#<syntax:11:24foo>-#<syntax:11:28 bar>|) #t)
这下真相大白了, 问题在于通过#'a#'b, 我们得到的其实是句法对象. 因此, 为了达成我们的预期, 我们应该使用syntax->datum进行转换.
> (define-syntax (hyphen-define/ok1 stx)
    (syntax-case stx ()
      [(_ a b (args ...) body0 body ...)
       (syntax-case (datum->syntax #'a
                                   (string->symbol (format "~a-~a"
                                                           (syntax->datum #'a)
                                                           (syntax->datum #'b))))
                    ()
         [name #'(define (name args ...)
                   body0 body ...)])]))
> (hyphen-define/ok1 foo bar () #t)
> (foo-bar)
#t
终于结束了! 接下来我们要引入一些帮助我们编写宏的简便方法.

第4.1.1小节 with-syntax

与其使用嵌套的syntax-case, 其实我们可以使用with-syntax. 从某种意义上说, with-syntax长得有点像let.

> (define-syntax (hyphen-define/ok2 stx)
    (syntax-case stx ()
      [(_ a b (args ...) body0 body ...)
       (with-syntax ([name (datum->syntax #'a
                                          (string->symbol (format "~a-~a"
                                                                  (syntax->datum #'a)
                                                                  (syntax->datum #'b))))])
         #'(define (name args ...)
             body0 body ...))]))
> (hyphen-define/ok2 foo bar () #t)
> (foo-bar)
#t
实际上, with-syntax可以看成是一种syntax-case的句法糖.
(with-syntax ([<pattern> <syntax>]) <body>)
差不多等价于
(syntax-case <syntax> () [<pattern> <body>])
所以这不是什么魔法!

第4.1.2小节 with-syntax*

with-syntax*之于with-syntax, 就像let*之于let.

> (require (for-syntax racket/syntax))
> (define-syntax (foo stx)
    (syntax-case stx ()
      [(_ a)
       (with-syntax* ([b #'a]
                      [c #'b])
         #'c)]))
读者需要注意的是, with-syntax*并没有由racket/base提供, 所以你需要(require (for-syntax racket/syntax)). {译注: 原文所说的令人困惑的错误消息, 现在已经变了, 变得相当正常:
> (define-syntax (foo stx)
    (syntax-case stx ()
      [(_ a)
       (with-syntax* ([b #'a]
                      [c #'b])
         #'c)]))
> (foo 0)
with-syntax*: undefined;
 cannot reference an identifier before its definition

第4.1.3小节 format-id

racket/syntax里面存在着一个叫做format-id的辅助函数, 其可以帮助我们更优雅地生成想要的标识符.

> (require (for-syntax racket/syntax))
> (define-syntax (hyphen-define/ok3 stx)
    (syntax-case stx ()
      [(_ a b (args ...) body0 body ...)
       (with-syntax ([name (format-id #'a "~a-~a" #'a #'b)])
         #'(define (name args ...)
             body0 body ...))]))
> (hyphen-define/ok3 bar baz () #t)
> (bar-baz)
#t
这免去了之前我们所体验到的诸多繁琐.

format-id的一个参数是词法上下文, 一般来说读者应该不会想在这里填上stx, 而会是更加特化的上下文信息, 例如这里是#'a.

第4.1.4小节 另一个例子

以下是一个变种, 其可以接受多个名称部分, 而将它们以连字号连接.

> (require (for-syntax racket/string racket/syntax))
> (define-syntax (hyphen-define* stx)
    (syntax-case stx ()
      [(_ (names ...) (args ...) body0 body ...)
       (let ([name-stxs (syntax->list #'(names ...))])
         (with-syntax ([name (datum->syntax (car name-stxs)
                                            (string->symbol
                                             (string-join (for/list ([name-stx name-stxs])
                                                            (symbol->string
                                                             (syntax-e name-stx)))
                                                          "-")))])
           #'(define (name args ...)
               body0 body ...)))]))
> (hyphen-define* (foo bar baz) (v) (* 2 v))
> (foo-bar-baz 50)
100
或许这里最值得注意的地方是我们提供给datum->syntax的词法上下文参数是什么.

第4.2节 制作我们自己的struct

现在让我们应用我们所学到的东西于一个更为实际的例子. 我们将会实现一个类似于struct的机制, 但远为简化.

对于以下的结构声明:

(our-struct name (field1 field2 ...))
我们需要定义以下的一些过程:
> (require (for-syntax racket/syntax))
> (define-syntax (our-struct stx)
    (syntax-case stx ()
      [(_ id (fields ...))
       (with-syntax ([pred-id (format-id #'id "~a?" #'id)])
         #`(begin
             ; Define a constructor.
             (define (id fields ...)
               (apply vector (cons 'id  (list fields ...))))
             ; Define a predicate.
             (define (pred-id v)
               (and (vector? v)
                    (eq? (vector-ref v 0) 'id)))
             ; Define an accessor for each field.
             #,@(for/list ([x (syntax->list #'(fields ...))]
                           [n (in-naturals 1)])
                  (with-syntax ([acc-id (format-id #'id "~a-~a" #'id x)]
                                [ix n])
                    #`(define (acc-id v)
                        (unless (pred-id v)
                          (error 'acc-id "~a is not a ~a struct" v 'id))
                        (vector-ref v ix))))))]))
; Test it out
> (require rackunit)
> (our-struct foo (a b))
> (define s (foo 1 2))
> (check-true (foo? s))
> (check-false (foo? 1))
> (check-equal? (foo-a s) 1)
> (check-equal? (foo-b s) 2)
> (check-exn exn:fail?
             (lambda () (foo-a "furble")))
; The tests passed.
; Next, what if someone tries to declare:
> (our-struct "blah" ("blah" "blah"))
format-id: contract violation
  expected: (or/c string? symbol? identifier? keyword? char?
number?)
  given: #<syntax:eval:83:0 "blah">
这里的错误信息不是很有用, 因为其是来源于format-id的, 算是一种实现细节.

你可能听说过syntax-case的语句可以包含一个可选的guard或者说fender表达式. 一个语句不仅可能是

[<pattern> <template>]
还可能是
[<pattern> <guard> <template>]
让我们为our-struct添加guard表达式.
> (require (for-syntax racket/syntax))
> (define-syntax (our-struct stx)
    (syntax-case stx ()
      [(_ id (fields ...))
       ; Guard or "fender" expression:
       (for-each (lambda (x)
                   (unless (identifier? x)
                     (raise-syntax-error #f "not an identifier" stx x)))
                 (cons #'id (syntax->list #'(fields ...))))
       (with-syntax ([pred-id (format-id #'id "~a?" #'id)])
         #`(begin
             ; Define a constructor.
             (define (id fields ...)
               (apply vector (cons 'id  (list fields ...))))
             ; Define a predicate.
             (define (pred-id v)
               (and (vector? v)
                    (eq? (vector-ref v 0) 'id)))
             ; Define an accessor for each field.
             #,@(for/list ([x (syntax->list #'(fields ...))]
                           [n (in-naturals 1)])
                  (with-syntax ([acc-id (format-id #'id "~a-~a" #'id x)]
                                [ix n])
                    #`(define (acc-id v)
                        (unless (pred-id v)
                          (error 'acc-id "~a is not a ~a struct" v 'id))
                        (vector-ref v ix))))))]))
; Now the same misuse gives a better error message:
> (our-struct "blah" ("blah" "blah"))
eval:86:0: our-struct: not an identifier
  at: "blah"
  in: (our-struct "blah" ("blah" "blah"))
之后, 我们将会看到syntax-parse做类似的事情将会更加容易.

第4.3节 为嵌套哈希查找使用点记号

之前的例子将一些标识符连接起来形成新的标识符, 而这里我们要做相反的事情: 将标识符拆成数个部分.

其他的语言中你经常可以看到点记法, 例如在JavaScript中使用JSON. 迭代使用点记法在Racket中的等价物往往是繁琐的, 例如

foo = js.a.b.c;
可能要写成
(hash-ref (hash-ref (hash-ref js 'a) 'b) 'c)
或许我们可以编写一个辅助函数, 使得类似的事情变得更为容易和清晰.
; This helper function:
> (define/contract (hash-refs h ks [def #f])
    ((hash? (listof any/c)) (any/c) . ->* . any)
    (with-handlers ([exn:fail? (const (cond [(procedure? def) (def)]
                                            [else def]))])
      (for/fold ([h h])
        ([k (in-list ks)])
        (hash-ref h k))))
; Lets us say:
> (hash-refs js '(a b c))
"value"
这已经是不错了, 但是或许我们还可以用宏做得更好.
; This macro:
> (require (for-syntax racket/syntax))
> (define-syntax (hash.refs stx)
    (syntax-case stx ()
      ; If the optional ‘default' is missing, use #f.
      [(_ chain)
       #'(hash.refs chain #f)]
      [(_ chain default)
       (let* ([chain-str (symbol->string (syntax->datum #'chain))]
              [ids (for/list ([str (in-list (regexp-split #rx"\\." chain-str))])
                     (format-id #'chain "~a" str))])
         (with-syntax ([hash-table (car ids)]
                       [keys       (cdr ids)])
           #'(hash-refs hash-table 'keys default)))]))
; Gives us "sugar" to say this:
> (hash.refs js.a.b.c)
"value"
; Try finding a key that doesn't exist:
> (hash.refs js.blah)
#f
; Try finding a key that doesn't exist, specifying the default:
> (hash.refs js.blah 'did-not-exist)
'did-not-exist
的确可行.

现在和之前一样, 我们希望能够使得错误信息提供有用的提示.

> (require (for-syntax racket/syntax))
> (define-syntax (hash.refs stx)
    (syntax-case stx ()
      ; Check for no args at all
      [(_)
       (raise-syntax-error #f "Expected hash.key0[.key1 ...] [default]" stx)]
      ; If the optional ‘default' is missing, use #f.
      [(_ chain)
       #'(hash.refs chain #f)]
      [(_ chain default)
       (unless (identifier? #'chain)
         (raise-syntax-error #f "Expected hash.key0[.key1 ...] [default]" stx #'chain))
       (let* ([chain-str (symbol->string (syntax->datum #'chain))]
              [ids (for/list ([str (in-list (regexp-split #rx"\\." chain-str))])
                     (format-id #'chain "~a" str))])
         ; Check that we have at least hash.key
         (unless (and (>= (length ids) 2)
                      (not (eq? (syntax-e (cadr ids)) '||)))
           (raise-syntax-error #f "Expected hash.key" stx #'chain))
         (with-syntax ([hash-table (car ids)]
                       [keys       (cdr ids)])
           #'(hash-refs hash-table 'keys default)))]))
; See if we catch each of the misuses
> (hash.refs)
eval:97:0: hash.refs: Expected hash.key0[.key1 ...]
[default]
  in: (hash.refs)
> (hash.refs 0)
eval:98:0: hash.refs: Expected hash.key0[.key1 ...]
[default]
  at: 0
  in: (hash.refs 0 #f)
> (hash.refs js)
eval:99:0: hash.refs: Expected hash.key
  at: js
  in: (hash.refs js #f)
> (hash.refs js.)
eval:100:0: hash.refs: Expected hash.key
  at: js.
  in: (hash.refs js. #f)
还算可以, 但是这种错误处理在某种意义上有点恼人, 使得逻辑的主干部分不那么明晰.

说到底, 很难说使用hash-refs还是hash.refs更加清晰, 但是至少Racket提供了这样的选择.

第5章 句法参数

照应if是一个流行的宏的例子. 例如, 我们可以将

(let ([tmp (big-long-calculation)])
  (if tmp
      (foo tmp)
      #f))
写成
(aif (big-long-calculation)
     (foo it)
     #f)
换言之, 当condition为真时, 标识符it会被自动创建并置为condition之值. 似乎定义这个宏可能很简单:
> (define-syntax-rule (aif condition true-expr false-expr)
    (let ([it condition])
      (if it
          true-expr
          false-expr)))
> (aif #t (displayln it) (void))
it: undefined;
 cannot reference an identifier before its definition
  in module: 'program
当然了, 这么简单不太可能.

第6章 racket/splicing的要义为何?

第7章 健壮的宏: syntax-parse

第7.1节 函数的错误处理策略

  1. 完全不处理:
    > (define (misuse s)
        (string-append s " snazzy suffix"))
    ; User of the function:
    > (misuse 0)
    string-append: contract violation
      expected: string?
      given: 0
      argument position: 1st
      other arguments...:
       " snazzy suffix"
    ; I guess I goofed, but – what is this "string-append" of which you
    ; speak??
  2. 手工编写错误处理代码:
    > (define (misuse s)
        (unless (string? s)
          (error 'misuse "expected a string, but got ~a" s))
        (string-append s " snazzy suffix"))
    ; User of the function:
    > (misuse 0)
    misuse: expected a string, but got 0
    ; I goofed, and understand why! It's a shame the writer of the
    ; function had to work so hard to tell me.
  3. 使用合同 (contract):
    > (define/contract (misuse s)
        (string? . -> . string?)
        (string-append s " snazzy suffix"))
    ; User of the function:
    > (misuse 0)
    misuse: contract violation
      expected: string?
      given: 0
      in: the 1st argument of
          (-> string? string?)
      contract from: (function misuse)
      blaming: program
       (assuming the contract is correct)
      at: eval:131.0
    ; I goofed, and understand why! I'm happier, and I hear the writer of
    ; the function is happier, too.
  4. 使用Typed Racket:
    #lang typed/racket
    > (: misuse (String -> String))
    > (define (misuse s)
        (string-append s " snazzy suffix"))
    > (misuse 0)
    eval:3:0: Type Checker: type mismatch
      expected: String
      given: Zero
      in: 0

第7.2节 宏的错误处理策略

对于宏而言, 我们也有着类似的选择.

  1. 完全不处理, 不过在宏的情况下错误信息可能更加糟糕.
  2. 手工编写错误处理代码, 但是既给编写者添加了负担, 也给阅读者增添了麻烦.
  3. 使用syntax-parse. 对于宏而言, 这相当于使用合同或者类型.

第7.3节 使用syntax-parse

原作者其实还没有写这个部分就是了, 而是建议阅读Introduction.

第8章 参考和致谢