Scheme巨集基礎入門(轉載)
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可以被用來定義函式中的區域性巨集
《SOD框架“企業級”應用資料架構實戰》