1. 程式人生 > 其它 >Scheme巨集基礎入門(轉載)

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的第二個引數是一個兩個元素的列表:

  1. 第一個元素:轉換前的程式碼,其中_代表巨集的名字
  2. 第二個元素:轉換後的程式碼

注意:如果把上面的程式碼可以寫成函式,但是因為閉包的原因,傳遞進去的引數實際上是不會改變的

(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框架“企業級”應用資料架構實戰