來源 https://www.cnblogs.com/GodWroteinlisp/archive/2012/03/27/2420426.htmlhtml
scheme目前其實有至少三套宏系統:linux
syntax-case徹底不受限制,擴展能力與傳統lisp宏(defmacro)是同樣的,但因爲它自帶模式匹配功能,因此寫起來會更方便,至少quasiquote,unquote,unquote-splicing少了不少.程序員
由於syntax-case的強大能力,用它來實現define-syntax 也是很簡單滴:web
以上代碼證實了scheme的清潔宏系統至少和傳統lisp宏系統同樣強大.express
那麼,能不能用非清潔宏系統來實現清潔宏呢?這篇文章有介紹:Hygiene for the Unhygienic編程
scheme裏的宏不一樣的實現有不一樣的寫法安全
來源 https://www.cnblogs.com/youxin/p/3427323.htmlapp
1.mzscheme的define-macro (mzscheme也就是pltschme,也就是drracket,沒有define-macro這個關鍵字)編輯器
語法:(define-macro macro-nameide
(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 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
#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。
(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 ...))]))
(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
(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)
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的特性。
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)>
C宏的優勢暫且不說,這裏只說下缺點。C的宏在某些狀況下,比較難以獲得安全,可靠的轉換後的代碼。
#define foo "hello printf(foo world")
這個宏連lexical tokens都不是完整的(此時一個完整的lexcial token爲"hello world"). 這對閱讀代碼,編輯器分析程序源碼都是很痛苦的事。咱們說這種宏:failed to respect the integrity of lexical tokens。
#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.
#define discriminant(a,b,c) ((b)*(b)-4*(a)*(c)) discriminant(3, x--, 2)
這種問題在C的宏系統裏沒法避免,只能靠程序員細心去避免。可是在scheme的宏裏,咱們能夠經過定義新的 變量的方式來避免這種問題。
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)。健康的宏能夠安全地在任何地方使用,沒必要擔憂與現有的變量名衝突。對於許多元編程任務,這個特性使宏更可預測並容易使用。
更多看原文。
更多:http://www.shido.info/lisp/scheme_syntax_e.html
============= End