强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第7章:宏系统

第 7 章:宏系统

7.1 宏基础

7.1.1 什么是宏

宏(Macro)是编译期的代码转换机制——它接收代码作为输入,输出转换后的代码。宏在求值之前进行展开,允许程序员扩展语言本身的语法。

┌─ 宏的工作流程 ─────────────────────────────────┐
│                                                 │
│  源代码           宏展开           求值         │
│  ┌────────┐    ┌──────────┐    ┌────────┐      │
│  │ (my-if │ →  │  (cond   │ →  │  结果  │      │
│  │  #t    │    │   (#t    │    │        │      │
│  │  1 2)  │    │    1)    │    │        │      │
│  └────────┘    │   (else  │    └────────┘      │
│                │    2))   │                     │
│                └──────────┘                     │
│                宏在编译期展开,                   │
│                不影响运行时性能                   │
│                                                 │
└─────────────────────────────────────────────────┘

7.1.2 宏与函数的区别

特性函数
求值时机运行时编译期(展开期)
参数求值先求值所有参数参数不预先求值
返回值计算结果代码(S-表达式)
调试简单较复杂
性能有调用开销展开后无额外开销
类型语法转换器
;; 函数:参数在调用前已求值
(define (my-if test then else)
  (cond (test then)
        (else else)))
;; 问题:then 和 else 都会被求值!
(my-if #t (display "yes") (display "no"))
;; 输出: yesno(两个分支都执行了)

;; 宏:可以选择性地展开
;; 宏定义见下节

7.2 syntax-rules 宏

syntax-rules 是 Scheme 标准的卫生宏(hygienic macro)机制。

7.2.1 基本语法

;; syntax-rules 的基本结构
;; (syntax-rules (字面量...)
;;   (模式 模板)
;;   (模式 模板)
;;   ...)

;; 模式匹配:_ 匹配任何内容
(define-syntax my-when
  (syntax-rules ()
    ((_ test body ...)
     (if test (begin body ...)))))

(my-when (> 3 2)
  (display "3 大于 2")
  (newline))
;; 展开为: (if (> 3 2) (begin (display "3 大于 2") (newline)))

7.2.2 模式变量与省略号

;; ... 表示"零个或多个"
(define-syntax my-list
  (syntax-rules ()
    ((_ item ...)
     (list item ...))))

(my-list 1 2 3 4)
;; 展开为: (list 1 2 3 4)

;; 多组省略号
(define-syntax my-pairs
  (syntax-rules ()
    ((_ (key val) ...)
     (list (cons key val) ...))))

(my-pairs (a 1) (b 2) (c 3))
;; 展开为: (list (cons 'a 1) (cons 'b 2) (cons 'c 3))
;; 注意:symbol 在这里不会自动引号化,需要额外处理

;; 字面量匹配
(define-syntax my-cond
  (syntax-rules (else)
    ((_ (else body ...))
     (begin body ...))
    ((_ (test body ...) rest ...)
     (if test
         (begin body ...)
         (my-cond rest ...)))))

(my-cond
  ((> 3 5) "大于")
  ((= 3 5) "等于")
  (else "小于"))

7.2.3 卫生宏(Hygienic Macros)

卫生宏是 Scheme 宏系统的核心特性——宏内部引入的变量不会与外部代码冲突。

;; 卫生宏的关键特性
(define-syntax swap!
  (syntax-rules ()
    ((_ a b)
     (let ((temp a))    ; temp 不会与外部的 temp 冲突
       (set! a b)
       (set! b temp)))))

(define temp 100)
(define x 1)
(define y 2)

(swap! x y)
;; x => 2, y => 1, temp => 100(temp 不受影响)

;; 非卫生宏的问题(Common Lisp 风格)
;; 如果 temp 不是卫生的,上面的 temp 100 会被覆盖

;; 对比:使用 gensym 确保唯一性(了解原理)
;; (define-syntax swap-unsafe
;;   (syntax-rules ()
;;     ((_ a b)
;;      (let ((t a))    ; t 可能与外部冲突!
;;        (set! a b)
;;        (set! b t)))))

注意syntax-rules 自动保证卫生性,无需手动管理变量名冲突。这是 Scheme 宏优于 Common Lisp defmacro 的关键所在。

7.3 define-syntax 进阶

7.3.1 多子句匹配

;; 带多子句的宏
(define-syntax my-let
  (syntax-rules ()
    ;; 简单 let
    ((_ ((var val) ...) body ...)
     ((lambda (var ...) body ...) val ...))
    ;; 命名 let
    ((_ name ((var val) ...) body ...)
     ((letrec ((name (lambda (var ...) body ...)))
        name)
      val ...))))

;; 使用简单 let
(my-let ((x 1) (y 2))
  (+ x y))
;; => 3

;; 使用命名 let
(my-let loop ((i 0) (acc 0))
  (if (> i 10)
      acc
      (loop (+ i 1) (+ acc i))))
;; => 55

7.3.2 递归宏

;; 递归展开的宏
(define-syntax or-mine
  (syntax-rules ()
    ((_) #f)
    ((_ e) e)
    ((_ e1 e2 ...)
     (let ((t e1))
       (if t t (or-mine e2 ...))))))

(or-mine #f #f 3 #f)
;; => 3

;; 条件编译宏
(define-syntax define-with-log
  (syntax-rules ()
    ((_ (name args ...) body ...)
     (define (name args ...)
       (format #t "调用 ~a~%" 'name)
       body ...))))

(define-with-log (add a b)
  (+ a b))

(add 3 4)
;; 输出: 调用 add
;; => 7

7.3.3 字面量集

;; 使用字面量集(literal sets)精确控制匹配
(define-syntax my-case
  (syntax-rules (else =>)
    ((_ expr)
     #f)
    ((_ expr (else body ...))
     (begin body ...))
    ((_ expr (vals => func) rest ...)
     (if (memv expr 'vals)
         (func expr)
         (my-case expr rest ...)))
    ((_ expr (vals body ...) rest ...)
     (if (memv expr 'vals)
         (begin body ...)
         (my-case expr rest ...)))))

(my-case 2
  ((1 3 5) "奇数")
  ((2 4 6) "偶数" "是偶数")
  (else "其他"))
;; => "偶数" "是偶数"(返回最后一个表达式的结果)

7.4 自定义宏实战

7.4.1 常用宏模式

;; 1. 表达式计时宏
(define-syntax time-it
  (syntax-rules ()
    ((_ label body ...)
     (let ((start (get-internal-real-time)))
       (let ((result (begin body ...)))
         (let ((end (get-internal-real-time)))
           (format #t "[~a] 耗时: ~a ms~%"
                   label
                   (/ (* (- end start) 1000)
                      internal-time-units-per-second))
           result))))))

(time-it "计算"
  (apply + (iota 1000000)))
;; 输出: [计算] 耗时: XX ms
;; => 499999500000

;; 2. 断言宏
(define-syntax assert
  (syntax-rules ()
    ((_ expr)
     (unless expr
       (error "断言失败:" 'expr)))
    ((_ expr msg)
     (unless expr
       (error msg 'expr)))))

(define x 5)
(assert (> x 0))     ; 通过
;; (assert (< x 0))  ; => ERROR: 断言失败: (< x 0)

;; 3. 解构绑定宏
(define-syntax let-alist
  (syntax-rules ()
    ((_ alist ((key var) ...) body ...)
     (let ((var (assq-ref alist 'key)) ...)
       body ...))))

(define config '((host . "localhost") (port . 8080) (debug . #t)))
(let-alist config ((host h) (port p) (debug d))
  (format #t "~a:~a debug=~a~%" h p d))
;; 输出: localhost:8080 debug=#t

;; 4. 组合子宏
(define-syntax compose-macros
  (syntax-rules ()
    ((_ f g)
     (syntax-rules ()
       ((_ args ...)
        (f (g args ...)))))))

;; 5. 条件定义宏
(define-syntax define-if-available
  (syntax-rules ()
    ((_ name module-ref fallback)
     (define name
       (catch #t
         (lambda ()
           (resolve-interface module-ref))
         (lambda _ fallback))))))

7.4.2 编译期计算

;; 在宏展开期进行计算
(define-syntax compile-time-constant
  (syntax-rules ()
    ((_ expr)
     (let-syntax ((val (syntax-rules ()
                         ((_) expr))))
       (val)))))

;; 实际应用:编译期字符串拼接
(define-syntax string-join-compile
  (syntax-rules ()
    ((_ sep s ...)
     (string-append s ...))))

;; 注:Guile 的 syntax-rules 能力有限,
;; 复杂编译期计算需要 syntax-case 或 define-macro

7.5 define-macro(传统宏)

Guile 也支持传统(非卫生)的 define-macro,类似于 Common Lisp 的 defmacro

;; define-macro(非卫生,需谨慎使用)
(define-macro (my-unless test . body)
  `(if (not ,test)
       (begin ,@body)))

(my-unless (< 3 2)
  (display "3 不小于 2")
  (newline))
;; 展开为: (if (not (< 3 2)) (begin (display "3 不小于 2") (newline)))

;; 对比 syntax-rules 和 define-macro

;; syntax-rules 版本(卫生)
(define-syntax s-unless
  (syntax-rules ()
    ((_ test body ...)
     (if (not test) (begin body ...)))))

;; define-macro 版本(非卫生,但更灵活)
(define-macro (m-unless test . body)
  `(if (not ,test)
       (begin ,@body)))

;; 非卫生宏的风险
(define-macro (dangerous-swap! a b)
  `(let ((temp ,a))
     (set! ,a ,b)
     (set! ,b temp)))

;; 如果外部有 temp 变量会出问题
(define temp 100)
(define p 1)
(define q 2)
(dangerous-swap! p q)
;; temp 可能被覆盖!

注意:优先使用 syntax-rules,只在 syntax-rules 无法实现时才使用 define-macro

7.6 syntax-case(高级宏)

syntax-case 是更强大的宏系统,支持运行时检查和代码生成。

(use-modules (ice-9 syncase))

;; syntax-case 允许在模式匹配中使用任意谓词
(define-syntax my-letrec
  (lambda (stx)
    (syntax-case stx ()
      ((_ ((var init) ...) body ...)
       (with-syntax ((tmp (generate-temporaries #'(var ...))))
         #'((lambda (var ...)
              (let ((tmp init) ...)
                (set! var tmp) ...
                body ...))
            (void) ...))))))

;; 带谓词的模式匹配
(define-syntax my-define
  (lambda (stx)
    (syntax-case stx ()
      ((_ name value)
       (identifier? #'name)
       #'(define name value))
      ((_ (name args ...) body ...)
       (identifier? #'name)
       #'(define (name args ...) body ...)))))

;; syntax-case 的优势:可以进行任意检查
(define-syntax check-type
  (lambda (stx)
    (syntax-case stx (integer string)
      ((_ integer expr)
       #'(let ((val expr))
           (unless (integer? val)
             (error "期望整数" val))
           val))
      ((_ string expr)
       #'(let ((val expr))
           (unless (string? val)
             (error "期望字符串" val))
           val)))))

(check-type integer 42)    ; => 42
;; (check-type integer "hi") ; => ERROR: 期望整数

7.7 宏调试

7.7.1 展开查看

;; 查看宏展开结果
(use-modules (ice-9 pretty-print))

;; 使用 macroexpand(Guile 中通过 Ice-9 提供)
(use-modules (system syntax internal))

;; 或者简单地在 REPL 中使用 ,pp
;; scheme@(guile-user)> ,pp (syntax->datum (expand '(my-when #t (display "hi"))))

;; 手动展开
(define-syntax test-macro
  (syntax-rules ()
    ((_ x) (+ x 1))))

;; 调试技巧:用 quote 返回宏的输出
(define-syntax debug-macro
  (syntax-rules ()
    ((_ x)
     (quote (+ x 1)))))  ; 返回代码而非执行结果

(debug-macro 5)  ; => (+ x 1)

7.7.2 常见宏错误

错误原因解决方案
Bad syntax模式不匹配检查模式中的变量和字面量
Unbound variable卫生性导致变量名不同使用 syntax-case 或重新设计
无限展开递归宏没有终止条件确保有基本情况
变量捕获使用 define-macro 时改用 syntax-rules

7.8 业务场景

7.8.1 DSL(领域特定语言)

;; 用宏创建 HTML DSL
(define-syntax html
  (syntax-rules (div p span h1 h2 ul li a)
    ((_ (div attrs ... body ...))
     (string-append "<div" (format-attrs attrs ...) ">"
                    (html body ...) ... "</div>"))
    ((_ (p body ...))
     (string-append "<p>" (html body ...) ... "</p>"))
    ((_ (h1 body ...))
     (string-append "<h1>" (html body ...) ... "</h1>"))
    ((_ text)
     (if (string? text) text (format #f "~a" text)))))

;; 实用的配置 DSL
(define-syntax define-config
  (syntax-rules ()
    ((_ name (key default) ...)
     (define name
       (let ((ht (make-hash-table)))
         (hash-set! ht 'key default) ...
         (lambda (msg . args)
           (case msg
             ((get) (hash-ref ht (car args)))
             ((set!) (hash-set! ht (car args) (cadr args)))
             ((all) ht))))))))

(define-config my-app-config
  (host "localhost")
  (port 8080)
  (debug #f))

(my-app-config 'get 'host)  ; => "localhost"
(my-app-config 'set! 'port 3000)
(my-app-config 'get 'port)  ; => 3000

7.8.2 测试框架宏

;; 简易测试框架
(define test-count 0)
(define pass-count 0)

(define-syntax test-case
  (syntax-rules ()
    ((_ name expr expected)
     (begin
       (set! test-count (+ test-count 1))
       (let ((result expr))
         (if (equal? result expected)
             (begin
               (set! pass-count (+ pass-count 1))
               (format #t "  ✓ ~a~%" name))
             (format #t "  ✗ ~a: 期望 ~a, 得到 ~a~%"
                     name expected result)))))))

(define-syntax test-suite
  (syntax-rules ()
    ((_ name test ...)
     (begin
       (format #t "=== ~a ===~%" name)
       test ...
       (format #t "结果: ~a/~a 通过~%"
               pass-count test-count)
       (set! test-count 0)
       (set! pass-count 0)))))

(test-suite "算术测试"
  (test-case "加法" (+ 1 2) 3)
  (test-case "乘法" (* 3 4) 12)
  (test-case "减法" (- 10 3) 7))
;; === 算术测试 ===
;;   ✓ 加法
;;   ✓ 乘法
;;   ✓ 减法
;; 结果: 3/3 通过

7.9 本章小结

主题要点
宏是什么编译期代码转换,操作代码本身
syntax-rules卫生宏,推荐使用
define-macro非卫生宏,灵活性高但需谨慎
syntax-case高级宏,支持谓词和代码生成
卫生性Scheme 宏系统的核心优势
宏调试查看展开结果,理解错误信息

扩展阅读


上一章:第 6 章:控制流 下一章:第 8 章:数据结构