2010-08-21 69 views
11

McCarthy's小學S-功能和謂語​​,eqcarcdrcons僅使用LISP基元定義defmacro函數?

接着,他添加到他的基本符號,使書寫了他所謂的S-功能:quotecondlambdalabel

在此基礎上,我們將稱之爲「LISP的原語」(雖然我開約型謂詞像numberp參數)

如何你會在你選擇的LISP中僅使用這些基元來定義defmacro函數嗎? (包括Scheme和Clojure)

+1

'Defmacro'是一個宏本身,而不是一個函數。 – Svante 2010-08-21 03:12:21

+0

@Svante不是真的:http://github.com/clojure/clojure/blob/b578c69d7480f621841ebcafdfa98e33fcb765f6/src/clj/clojure/core.clj#L370 但它確實有一些可疑的宏觀像Java的基礎。這是一個令人費解的混亂。 – Isaac 2010-08-21 04:40:17

+0

@J G你的問題總是很有趣!愛參與回答他們的想法:) – Isaac 2010-08-21 04:41:46

回答

5

試圖在像McCarthy的LISP機器這樣的機器上執行此操作的問題在於,沒有辦法在運行時阻止參數評估,並且無法在編譯時更改這些東西(這是宏執行的操作:它們在編譯之前重新排列代碼,基本上)。

但是這並不能阻止我們在運行時在McCarthy的機器上重寫我們的代碼。訣竅是引用我們傳遞給我們的「宏」的參數,所以它們不會被評估。

作爲一個例子,我們來看看我們可能想要的函數; unless。我們的理論函數有兩個參數,pq,並且返回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是假的。如果我們通過qp作爲函數的參數,我們不能這樣做。

但是我們可以在他們將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的其他代碼。

2

詳細解釋它的所有細節需要很多空間和時間才能在這裏找到答案,但大綱非常簡單。每個LISP最終的核心就像READ-EVAL-PRINT循環一樣,這就是說,一個元素一個元素,一個元素一個元素,解釋它,並且改變狀態 - 無論是在內存中還是打印結果。

讀部分着眼於每個元素閱讀和做一些事情吧:(?)

(cond ((atom elem)(lambda ...)) 
     ((function-p elem) (lambda ...))) 

爲了解釋宏,你只需要實現放入存儲宏某處的模板文本的功能,這個repl循環的謂詞 - 意思是簡單地定義一個函數 - 表示「哦,這是一個宏!」,然後將該模板文本複製回讀取器,以便解釋它。

如果你真的想看到多毛的細節,請閱讀計算機程序的結構和解釋或閱讀Queinnec的Lisp in Small PIeces

+0

是否SCICP走在關於宏的深度?通過快速搜索,我無法在PDF中找到很多關於它們的內容。 – Isaac 2010-08-21 04:40:58

+0

這可能是過於簡單化了,但是你是否在說如果我編寫了自己的eval函數,那麼我會是80%的方法,前提是我的eval函數知道什麼是宏聲明,以及後續調用該宏是什麼。 – hawkeye 2010-08-21 13:32:46

+0

如果您希望宏的語義與CL/Clojure匹配,那麼'eval'將首先在窗體上調用'macroexpand-all',然後將結果傳遞給不需要了解宏的原始eval。 我已經用這種方式實現了一個小的lisp,它工作得很好。 – Brian 2010-08-21 14:48:23