scheme里的宏不同的實現有不同的寫法:
1.mzscheme的define-macro (mzscheme也就是pltschme,也就是drracket,沒有define-macro這個關鍵字)
語法:(define-macro macro-name
(lambda macro-args)
macro-body ......)
例如:定義when
(define-macro when
(lambda (test . branch)
`(if ,test
(begin ,@brach))))
其中“·”重音號引入模版,逗號開始的符號為參數,逗號和@開始的被當為列表。
2.MIT的define-syntax和syntax-rules
語法:(define macro-name
(syntax-rules ()
( (template) operation)
......) )
上面的when的定義:
(define-syntax when
(syntax-rules ()
((when test expr ...) (if test (begin expr ...)))))
when的定義非常簡潔,主要是這種語法的模版非常直觀,其中“...”就可以表示有多個參數。
r5rs文檔里面寫的:
Syntax definitions are valid only at the top level of a <program>. They have the following form:
(define-syntax <keyword> <transformer spec>)
<Keyword> is an identifier, and the <transformer spec> should be an instance of syntax-rules. The top-level syntactic environment is extended by binding the <keyword> to the specified transformer.
There is no define-syntax analogue of internal definitions.
Although macros may expand into definitions and syntax definitions in any context that permits them, it is an error for a definition or syntax definition to shadow a syntactic keyword whose meaning is needed to determine whether some form in the group of forms that contains the shadowing definition is in fact a definition, or, for internal definitions, is needed to determine the boundary between the group and the expressions that follow the group. For example, the following are errors:
(define define 3)
(begin (define begin list))
(let-syntax
((foo (syntax-rules ()
((foo (proc args ...) body ...)
(define proc
(lambda (args ...)
body ...))))))
(let ((x 3))
(foo (plus x y) (+ x y))
(define foo x)
(plus foo x)))
Drracket宏:
http://docs.racket-lang.org/guide/macros.html
- 最簡單的創建一個宏的方式是使用 define-syntax-rule :
(define-syntax-rule pattern template) ;; 一個具體的例子,交換兩個變量的值 (define-syntax-rule (swap a b) (let ([tmp a]) (set! a b) (set! b tmp)))
pattern中的symbol成為 pattern variable, 在template中所有的pattern variable會被具體的 實際調用時候的語法對象所替代。使用:
(define x 1)
(define y 2)
(swap x y)
x :2
y:1 - The define-syntax-rule form binds a macro that matches a single pattern. The pattern must always start with an open parenthesis followed by an identifier, which is swap in this case. After the initial identifier, other identifiers are macro pattern variables that can match anything in a use of the macro. Thus, this macro matches the form (swap form1form2) for any form1 and form2.
- 在drracket下:
-
#lang racket (define-syntax-rule (swap a b) (let ((tmp a)) (set! a b) (set! b tmp))) (let ((x 1)(y 2)) (swap x y) (list x y))
define-syntax 和 syntax-rules :
define-syntax 和 syntax-rules : define-syntax-rule 只能匹配一個pattern,但是使用define-syntax和syntax-rules,我們 可以在一個宏里寫出多個pattern-template。
- 我們 可以在一個宏里寫出多個pattern-template。
(define-syntax id (syntax-rules (literal-id ...) [pattern template] ...)) ;;一個具體的例子 (define-syntax rotate (syntax-rules () [(rotate a b) (swap a b)] [(rotate a b c) (begin (swap a b) (swap b c))]))
pattern可以支持sequence … , 用來表示一個或者多個syntax object.
(define-syntax rotate (syntax-rules () [(rotate a) (void)] [(rotate a b c ...) (begin (swap a b) (rotate b c ...))]))
- procedure macro: 使用syntax-rules我們不能對pattern variable做更多判斷(譬如判斷macro的參數是否合法等),不能對 template做更多操作。
(define-syntax swap (lambda (stx) (syntax-case stx () [(swap x y) (if (and (identifier? #'x) (identifier? #'y)) #'(let ([tmp x]) (set! x y) (set! y tmp)) (raise-syntax-error #f "not an identifier" stx (if (identifier? #'x) #'y #'x)))])))
這里對swap參數做了檢查,如果這樣調用 (swap 10 b) 將會報錯,因為10不是一個identifier
- transformer: define-syntax 創建一個 transformer ,且綁定一個名字,這個綁定的名字能在編譯的時候 用來展開表達式(expand expression)。
(define-syntax a (lambda (stx) #'(printf "zh\n"))) (a)
當然transformer一般使用syntax-rules定義,syntax-rules返回的是一個procedure:
(syntax-rules () [(nothing) something]) #<procedure>
(syntax-case #'(+ 1 2) () [(op n1 n2) #'(- n1 n2)]) '(- 1 2)
3 Hygienic macros
Hygienic(安全)是對Scheme Macro系統描述用的最多的一個詞,一般來說,hygienic macros用來表示 表示宏的展開式里引入的變量不會和宏所使用的環境中的變量名沖突。
一個比較准確的描述:
If a macro transformer inserts a binding for an identifier, the new binding will not capture other identifiers of the same name introduced elsewhere.
舉例來說:
(define-syntax-rule (swap a b) (let ([tmp a]) (set! a b) (set! b tmp))) (let ([tmp 100] [b 200]) (swap tmp b) (printf "~a\n" (list tmp b)))
(swap tmp b) 展開后定義了一個局部變量 tmp ,他們會swap所使用的環境中的 tmp 不會有 任何關系,他們不會發生沖突。
另一個常用來形容scheme macro特點的詞匯是 referential transparency ,如果一個宏展開式 中引用了一個free variable(非local variable), 那么這個free variable將和宏定義的環境綁定, 而和宏具體使用環境無關。這有點像lexical scoping。
(define-syntax-rule (swap a b) (let ([tmp a]) (set! a b) (set! b tmp))) (let ([set! 100] [b 200]) (swap set! b) (printf "~a\n" (list set! b)))
在swap的定義里使用了let, set!這兩個free variable,他們綁定的是swap定義處的環境,為global namespace。 在swap使用的環境中,可以看到set!已經被定義成一個number,但是這不會對swap展開式中的set!有任何影響。
當然現在一般使用 hygienic macro 同時表示上面兩個scheme macro的特性。
4 Syntax Object:
macro transformer的輸入輸出為syntax object。一個S-exp對應的syntax object包含了值:(quote S-exp), source-location,lexical-information(用來保證hygienic特性). source-location一般是parse 源代碼的時候 加入(看另一篇文章racket reader). 創建一個syntax Object很簡單:
(syntax (+ 1 2)) #<syntax:1:0 (+ 1 2)>
6 Scheme Macro 與 C Macro的比較
C宏的優點暫且不說,這里只說下缺點。C的宏在某些情況下,比較難以得到安全,可靠的轉換后的代碼。
- C的宏允許我們在宏的實現里寫入任意字符串。
#define foo "hello printf(foo world")
這個宏連lexical tokens都不是完整的(此時一個完整的lexcial token為"hello world"). 這對閱讀代碼,編輯器分析程序源碼都是很痛苦的事。我們說這種宏:failed to respect the integrity of lexical tokens。
- C的宏可以展開成任意詞法序列:
#define mul(a, b) a*b add(x-y, x+y) exand to: x-y*x+y
正因為此,我們在初次學習C的宏的時候,就會被告知,宏的實現體里一定要把參數括起來!但即便如此, 我在實際工作中還是出現了忘了括小括號,結果導致了錯誤。這種現象叫做:failed to respect the structure of expressions。
- 我們在宏內使用的名字可能和宏使用的環境的名字沖突:
#define swap(v, w) { int tmp = (v);\ (v) = (w); (w) = tmp;} int tmp = 100; int atmp = 200; swap(tmp, atmp);
所以,我們在學習C宏的時候還是被告知,宏內引入的名字(這里譬如tmp)應該使用一個比較特殊的名字, 譬如_tmp.但是這只能說是一個權宜之計,當swap這個宏在另一個宏內使用,而令一個宏恰巧也使用了 _tmp這個變量,那么還是有可能造成錯誤。這種現象叫做:fail to respect the correlation between bindings and uses of names.
- 當宏的參數是一個expression的時候,可能有side effect
#define discriminant(a,b,c) ((b)*(b)-4*(a)*(c)) discriminant(3, x--, 2)
這種問題在C的宏系統里無法避免,只能靠程序員細心去避免。但是在scheme的宏里,我們可以通過定義新的 變量的方式來避免這種問題。
7 總結:
Scheme Macro非常強大,安全,成熟,他也成為很多其他語言提供宏機制的參考之一。當然也有缺點,就目前我的認識,我認為最為困難的地方在於難以掌握,但是一旦掌握 了Scheme Macro背后的實現,很多難以理解的地方也就豁然開朗。之后我將在寫兩篇文章,一是深入Scheme Macro,二是聊聊 Scheme Macro的實現。
轉自:http://blog.csdn.net/cnnzp/article/details/8307798
---------------------
一篇非常好的文章:
http://www.ibm.com/developerworks/cn/linux/l-metaprog2.html
syntax-case 宏並不是 Scheme 的標准部分,但是它們是使用最廣泛的宏類型,允許健康和非健康形式,與標准的 syntax-rules 宏密切相關。
syntax-case 宏采用清單 1 所示的形式:
(define-syntax macro-name
(lambda (x)
(syntax-case x (other keywords go here if any)
(
;;First Pattern
(macro-name macro-arg1 macro-arg2)
;;Expansion of macro (one or multiple forms)
;;(syntax is a reserved word)
(syntax (expansion of macro goes here))
)
(
;;Second Pattern -- a 1-argument version
(macro-name macro-arg1)
;;Expansion of macro
(syntax (expansion of macro goes here))
)
)))
|
這種形式將 macro-name 定義為用於進行轉換的關鍵字。用 lambda 定義的函數由宏轉換器用來將表達式轉換為展開形式。
syntax-case 以表達式 x 作為第一個參數。第二個參數是關鍵字列表,這些關鍵字在語法模式中采用字面意義。模式中使用的其他標識符用作模板變量。然后,syntax-case 接受一系列模式/轉換器組合。它依次通過每個組合進行處理,嘗試將輸入形式與模式進行匹配,如果匹配的話,它就產生相關聯的展開形式。
我們來看一個簡單的示例。假設我們想編寫一個比 Scheme 提供的版本更詳細的 if 語句版本。還假設我們想尋找兩個變量中比較大的一個並返回它。代碼如下:
(if (> a b) a b)
對於非 Scheme 程序員,沒有明顯的文字可以指出哪個是 “then” 分支,哪個是 “else” 分支。為了幫助他們理解代碼,可以創建定制的 if 語句,添加 “then” 和 “else” 關鍵字。代碼如下:
(my-if (> a b) then a else b)
清單 2 演示執行此操作的宏:
;;define my-if as a macro
(define-syntax my-if
(lambda (x)
;;establish that "then" and "else" are keywords
(syntax-case x (then else)
(
;;pattern to match
(my-if condition then yes-result else no-result)
;;transformer
(syntax (if condition yes-result no-result))
)
)))
|
在這個宏執行時,它按照以下形式將 my-if 表達式與模板進行匹配(換句話說,將宏調用與宏定義模式進行匹配):
(my-if (> a b) then a else b) | | | | | | | | | | | | v v v v v v (my-if condition then yes-result else no-result) |
因此,在轉換表達式中,任何出現 condition 的地方就替換為 (> a b)。(> a b) 是否是一個列表並不重要。它是包含列表中的一個元素,所以它在模式中作為一個單元。產生的 syntax 表達式只是將每個部分重新安排成一個新的表達式。
這種轉換發生在執行之前,這個時期稱為宏展開時期(macro-expansion time)。在許多基於編譯器的 Scheme 實現中,宏展開在編譯時發生。這意味着宏只在程序開始時或編譯時執行一次,以后不必再次執行。因此,我們的 my-if 語句沒有運行時開銷 —— 它在運行時轉換為一個簡單的 if。
在下一個示例中,我們要執行 swap! 宏。這個簡單的宏要交換兩個標識符的值。清單 3 給出了使用這個宏的示例。
(define a 1) (define b 2) (swap! a b) (display "a is now ")(display a)(newline) (display "b is now ")(display b)(newline) |
這個簡單的宏(清單 4)通過引入一個新的臨時變量來實現交換:
;;Define a new macro
(define-syntax swap!
(lambda (x)
;;we don't have any keywords this time
(syntax-case x ()
(
(swap! a b)
(syntax
(let ((c a))
(set! a b)
(set! b c)))
)
)))
|
這個宏引入了一個名為 c 的新變量。但是,如果要交換的參數之一正好也名為 c,那么會怎么樣?
syntax-case 解決這個問題的辦法是在宏展開時將 c 替換為一個惟一的未使用的變量名。因此,語法轉換器會自己負責這個問題。
注意,syntax-case 沒有替換 let。這是因為 let 是一個全局定義的標識符。
用不沖突的名稱替換引入的變量名,這種方法稱為健康的(hygiene);產生的宏稱為健康的宏(hygienic macros)。健康的宏可以安全地在任何地方使用,不必擔心與現有的變量名沖突。對於許多元編程任務,這個特性使宏更可預測並容易使用。
更多看原文。
