Scheme的宏比Lisp的宏简单,但是它有些看起来奇怪的“语法”却很少有文章进行过解释或者文章说了这点却容易忽略,使得我以前对Scheme宏的学习一直摸不到头脑,在看了很多篇有关Scheme宏的介绍文章后,觉得这篇文章写的是最容易理解的了,虽然不能算浅显易懂,有可能宏这个东西说得浅显了就不太容易懂。原文地址:Syntax宏 · 大专栏 (dazhuanlan.com) 。另外一篇Scheme官方介绍宏使用的文章链接:Syntactic Extension (scheme.com)
正文开始---------
宏 是 用户自定义的语法 ,而 Lisp/Scheme 提供的宏远比其他编程语言要强大的多。 使用宏可以让代码漂亮和紧凑
本质上来说宏就是一种 代码转换器 : 代码在被解释或编译前被转换成另外一种形式去执行
在 Scheme 语言中, 在 R5R5 规范以后简单的宏可以被方便地使用 syntax-rules 形式来定义,作为对比 Common Lisp 的宏显得要复杂许多:
- 使用 syntax-rules 可以更 直接 地定义宏,而不需要考虑诸如 变量捕捉 等细节
- 定义复杂的宏,使用 syntax-rules 比起 Common Lisp 的宏来说会困难得多(某些 Scheme 实现提供了 define-macro )
简单宏
把某个变量赋值为 '()
(define-syntax nil! (syntax-rules () ;; 转换前和转换后的列表 ((_ x) ;; 转换前的代码,_ 表示 宏的名字 (set! x '())))) ;; 转换后的代码 ;; (define a 1) ;; a ; => 1 ;; (nil! a) ;; a ; => ()
syntax-rules 的 第二个参数 是一个两个元素的 列表 :
- 第一个元素:转换前的代码,其中 _ 代表宏的名字
- 第二个元素:转换后的代码
注意:如果把上面的代码可以写成函数,但是因为闭包的原因,传递进去的参数实际上是不会改变的
(define (f-nil! x) (set! x '())) ;; (define a 1) ;; a ; => 1 ;; (f-nil! a) ; => () ;; a ; => 1 ;; (set! a '()) ;; a ; => ()
当谓词为真的时候,对接下来的表达式求值:
(define-syntax when (syntax-rules () ((_ pred b1 ...) ; ... 含义是任意个表达式,可以是0个 (if pred (begin b1 ...))))) ;; (let ((i 0)) ;; (when (= i 0) ;; (display "i == 0") ;; (newline))) ;; => i == 0 ;; ;Unspecified return value
上面的代码无法用函数来写,因为这是把代码转换到另外一种形式
可以用定义好的宏来再次定义宏:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; while宏:表示条件成立的循环 ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-syntax while (syntax-rules () ((_ pred b1 ...) (let loop () (when pred b1 ... (loop)))))) ;; (let ((i 0)) ;; (while (< i 10) ;; (display i) ;; (display #Space) ;; (set! i (+ i 1)))) ;; => 0 1 2 3 4 5 6 7 8 9 ;; ;Unspecified return value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; for宏:表示数字在范围之内的循环 ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-syntax for (syntax-rules () ((_ (i from to) b1 ...) (let loop((i from)) (when (< i to) b1 ... (loop (1+ i))))))) ;; (for (i 0 10) ;; (display i) ;; (display #Space)) ;; => 0 1 2 3 4 5 6 7 8 9 ;; ;Unspecified return value
多种模式
syntax-rules 可以支持定义多种模式。incf 宏是增加变量的值,如果不传入增加的值,就默认增加 1, 如果给定,就增加给定的值:
;; incf宏 (define-syntax incf (syntax-rules () ((_ x) (begin (set! x (+ x 1)) x)) ; 如果不给增加参数,默认增加1 ((_ x i) (begin (set! x (+ x i)) x)))) ;; (let ((i 0) (j 0)) ;; (incf i) ;; (incf j 3) ;; (display (list 'i '= i)) ;; (newline) ;; (display (list 'j '= j))) ;; => (i = 1) ;; (j = 3) ;; ;Unspecified return value
递归定义
syntax-rules 支持递归定义宏:
(define-syntax my-and (syntax-rules () ((_) #t) ((_ e) e) ((_ e1 e2 ...) (if e1 (my-and e2 ...) #f)))) ;; (my-and) ; => #t ;; (my-and #f) ; => #f ;; (my-and (> 2 1)) ; => #t ;; (my-and #t #f) ; => #f ;; (my-and #t (> 2 1)) ; => #t ;; (my-and #t (> 2 1) (< 3 2) (= 1 1))
(define-syntax my-or (syntax-rules () ((_) #f) ((_ e) e) ((_ e1 e2 ...) (let ((t e1)) (if t t (my-or e2 ...)))))) ;; (my-or) ; => #f ;; (my-or #t) ; => #t ;; (my-or (< 2 1)) ; => #f ;; (my-or #f #f) ; => #f ;; (my-or #f (> 2 1)) ; => #t ;; (my-or #f (> 2 1) (< 3 2) (= 1 1)) ; => #t
保留关键字
syntax-rules 的第一个参数是一组 保留关键字 的列表,这些关键字在转换的时候不会被替换。下面是自定义的 my-cond 宏, else 就是这个宏的保留关键字:
(define-syntax my-cond (syntax-rules (else) ((_ (else e1 ...)) (begin e1 ...)) ((_ (e1 e2 ...)) (when e1 e2 ...)) ((_ (e1 e2 ...) c1 ...) (if e1 (begin e2 ...) (cond c1 ...))))) ;; (my-cond (else (+ 1 2))) ; => 3 ;; (my-cond ((> 1 0) (+ 1 2))) ; => 3 ;; (my-cond ((< 1 0) (+ 1 2))) ; => ;Unspecified return value ;; (my-cond ((< 1 0) (+ 1 2)) ;; ((> 1 0) (+ 2 3))) ; => 5 ;; (my-cond ((< 1 0) (+ 1 2)) ;; (else (+ 2 3))) ; => 5
局部宏
let-syntax 和 letrec-syntax 可以被用来定义函数中的 局部宏