McCarthy's小學S-功能和謂語,eq
,car
,cdr
,cons
僅使用LISP基元定義defmacro函數?
接着,他添加到他的基本符號,使書寫了他所謂的S-功能:quote
,cond
,lambda
, label
在此基礎上,我們將稱之爲「LISP的原語」(雖然我開約型謂詞像numberp
參數)
如何你會在你選擇的LISP中僅使用這些基元來定義defmacro
函數嗎? (包括Scheme和Clojure)
McCarthy's小學S-功能和謂語,eq
,car
,cdr
,cons
僅使用LISP基元定義defmacro函數?
接着,他添加到他的基本符號,使書寫了他所謂的S-功能:quote
,cond
,lambda
, label
在此基礎上,我們將稱之爲「LISP的原語」(雖然我開約型謂詞像numberp
參數)
如何你會在你選擇的LISP中僅使用這些基元來定義defmacro
函數嗎? (包括Scheme和Clojure)
試圖在像McCarthy的LISP機器這樣的機器上執行此操作的問題在於,沒有辦法在運行時阻止參數評估,並且無法在編譯時更改這些東西(這是宏執行的操作:它們在編譯之前重新排列代碼,基本上)。
但是這並不能阻止我們在運行時在McCarthy的機器上重寫我們的代碼。訣竅是引用我們傳遞給我們的「宏」的參數,所以它們不會被評估。
作爲一個例子,我們來看看我們可能想要的函數; unless
。我們的理論函數有兩個參數,p
和q
,並且返回q
,除非p
爲真。如果p
爲真,則返回零。
一些例子(Clojure中的語法,但是這不會改變任何東西):
(unless (= "apples" "oranges") "bacon")
=> "bacon"
(unless (= "pears" "pears") "bacon")
=> nil
所以起初我們可能要編寫unless
作爲一個函數:
(defn unless [p q]
(cond p nil
true q))
,這似乎工作得很好:
(unless true 6)
=> nil
(unless false 6)
=> 6
而且隨着麥卡錫的LISP,它只會工作精細。問題是我們現代Lisp中不僅沒有副作用代碼,所以所有傳遞給unless
的參數都會被評估,無論我們是否希望它們是有問題的。事實上,即使在McCarthy的LISP中,如果評估其中一個參數需要年齡,那麼這可能會成爲一個問題,我們只想很少這樣做。但這是一個副作用問題。
因此,我們希望我們的unless
評估並返回q
只有如果p
是假的。如果我們通過q
和p
作爲函數的參數,我們不能這樣做。
但是我們可以在他們將quote
傳遞給我們的函數之前,阻止他們的評估。我們可以使用eval
(也被定義,僅使用引用論文中稍後基元定義的基元和其他函數)的功能來評估我們需要什麼,以便在需要時進行評估。
因此,我們有一個新的unless
:
(defn unless [p q]
(cond (eval p) nil
true (eval q)))
我們用它有點不同:
(unless (quote false) (quote (println "squid!")))
=> "squid" nil
(unless (quote true) (quote (println "squid!")))
=> nil
有你有什麼可以慷慨地被稱爲宏。
但這不是defmacro
或其他語言的等價物。這是因爲在McCarthy的機器上,在編譯期間沒有辦法執行代碼。如果您使用eval
函數來評估代碼,則無法知道不會評估「宏」函數的參數。閱讀和評價之間的差異與現在不同,雖然這個想法在那裏。 「重寫」代碼的能力就在那裏,在quote
的清涼中以及與eval
一起的列表操作中,但它並沒有像現在這樣被用在語言中(我稱之爲語法糖,幾乎是:只是引用你的論點,而你已經擁有了宏觀系統的力量。)
我希望我已經回答了你的問題,而沒有試圖自己定義一個像樣的defmacro
。如果你真的想看到這一點,我會告訴你在Clojure源代碼中很難操作的source for defmacro
,或者是Google的其他代碼。
詳細解釋它的所有細節需要很多空間和時間才能在這裏找到答案,但大綱非常簡單。每個LISP最終的核心就像READ-EVAL-PRINT循環一樣,這就是說,一個元素一個元素,一個元素一個元素,解釋它,並且改變狀態 - 無論是在內存中還是打印結果。
讀部分着眼於每個元素閱讀和做一些事情吧:(?)
(cond ((atom elem)(lambda ...))
((function-p elem) (lambda ...)))
爲了解釋宏,你只需要實現放入存儲宏某處的模板文本的功能,這個repl循環的謂詞 - 意思是簡單地定義一個函數 - 表示「哦,這是一個宏!」,然後將該模板文本複製回讀取器,以便解釋它。
如果你真的想看到多毛的細節,請閱讀計算機程序的結構和解釋或閱讀Queinnec的Lisp in Small PIeces。
是否SCICP走在關於宏的深度?通過快速搜索,我無法在PDF中找到很多關於它們的內容。 – Isaac 2010-08-21 04:40:58
這可能是過於簡單化了,但是你是否在說如果我編寫了自己的eval函數,那麼我會是80%的方法,前提是我的eval函數知道什麼是宏聲明,以及後續調用該宏是什麼。 – hawkeye 2010-08-21 13:32:46
如果您希望宏的語義與CL/Clojure匹配,那麼'eval'將首先在窗體上調用'macroexpand-all',然後將結果傳遞給不需要了解宏的原始eval。 我已經用這種方式實現了一個小的lisp,它工作得很好。 – Brian 2010-08-21 14:48:23
'Defmacro'是一個宏本身,而不是一個函數。 – Svante 2010-08-21 03:12:21
@Svante不是真的:http://github.com/clojure/clojure/blob/b578c69d7480f621841ebcafdfa98e33fcb765f6/src/clj/clojure/core.clj#L370 但它確實有一些可疑的宏觀像Java的基礎。這是一個令人費解的混亂。 – Isaac 2010-08-21 04:40:17
@J G你的問題總是很有趣!愛參與回答他們的想法:) – Isaac 2010-08-21 04:41:46