2017-08-25 78 views
0

我需要編寫一個帶有符號和列表的Common Lisp宏。該列表由兩個元素的列表組成:一個符號和一個字符串,像這樣:如何管理Common Lisp宏中的遞歸

((X "foo") (Y "bar") (Z "qwerty")) 

宏作品遞歸,它搜索符號列表,如果發現symbol回報T,否則返回NIL 。我寫這個代碼:

(defmacro already-exist (symbol my-list) 
    (cond ((null (eval my-list)) NIL) 
     ((eql (caar (eval my-list)) symbol) 
    T) 
    (T `(already-exist symbol ,(cdr (eval my-list)))))) 

但問題出在遞歸部分。事實上,如果我嘗試使用不包含symbol的列表作爲第一個元素的一部分運行宏,則會獲得錯誤。例如:

(defparameter listt '((X "foo") (Y "bar") (Z "qwerty"))) 

(already-exist Y listt) 

我得到的錯誤是「非法函數調用」。我認爲這是因爲宏嘗試將Y作爲函數調用進行評估。我怎麼能解決這個問題?通常,在Common Lisp中編寫遞歸宏的最佳方式是什麼?

+3

你應該爲此使用一個函數。宏用於生成代碼。您也可以使用'ASSOC'(或'(member ...:key#'first)')來查看密鑰是否已經存在。 – jkiiski

+0

@jkiiski我需要將一個未加引號的符號傳遞給宏,我不希望代碼評估該符號,所以出於這個原因,我選擇編寫一個宏而不是函數。有什麼方法可以在函數中獲得相同的行爲?如果你知道,請告訴我。無論如何,我不知道ASSOC,所以謝謝!我會記住的! – Jim

+2

您應該只引用符號或使用關鍵字。搞亂看起來像一個函數的表單的評估會讓人們閱讀代碼時感到困惑。 – jkiiski

回答

3

什麼是實際錯誤?

實際的錯誤是這樣的:

CL-USER 3 > (already-exist Y listt) 

Error: Illegal car (Y "bar") in compound form ((Y "bar") (Z "qwerty")). 

所以您要執行這個表達,這是無效的Lisp代碼:

((Y "bar") (Z "qwerty"))

錯誤1:

您的代碼將獲取my-list的象徵,你評估。下次它得到一個你不應該評估的列表。

錯誤2:

您創建一個形式symbol,而不是symbol值。

嘗試修復,導致一個醜陋的解決方案

  • 企圖只能評估my-list當它有一個符號作爲值
  • 通過把一個逗號在它前面經過的symbol

實施例:

CL-USER 2 > (defmacro already-exist (symbol my-list)        
       (when (symbolp my-list)           
       (setf my-list (eval my-list)))         
       (cond ((null my-list)            
        NIL)              
        ((eql (caar my-list) symbol)         
        T)               
        (T `(already-exist ,symbol ,(cdr my-list))))) 
ALREADY-EXIST 

CL-USER 3 > (defparameter listt '((X "foo") (Y "bar") (Z "qwerty"))) 
LISTT 

CL-USER 4 > (already-exist Y listt) 
T 

CL-USER 5 > (already-exist A listt) 
NIL 

但它沒有什麼意義,併產生新的問題。

編寫遞歸宏調用eval的方式很差。 eval無法獲得詞法綁定的價值,因爲評估不會發生在詞法環境中。

使用功能

CL-USER 10 > (member 'y listt :key #'car) 
((Y "bar") (Z "qwerty")) 

這意味着真正

4

我需要編寫一個Common Lisp宏[...]宏以遞歸方式工作。

你並不真的需要一個宏,其實如果你的值,在運行時才知道宏也解決不了問題。如果您希望他們未被評估,您需要引用符號。

... (eval my-list) ...

調用從宏觀的eval是一個很大的代碼味道。您的宏正在使用代碼,list符號在這一點上並不意味着什麼,只是它是一個符號。該宏很可能是而不是將在list必須有意義的值(此外,eval工作在空詞彙環境中)的環境中進行擴展。

有可能有一個遞歸膨脹,但本身宏不是遞歸:

* (defmacro foo (x) (foo x)) 
; in: DEFMACRO FOO 
;  (FOO X) 
; 
; caught STYLE-WARNING: 
; undefined function: FOO 
; 
; compilation unit finished 
; Undefined function: 
;  FOO 
; caught 1 STYLE-WARNING condition 
STYLE-WARNING: 
    FOO is being redefined as a macro when it was previously assumed to be a function. 
* (foo 3) 

debugger invoked on a UNDEFINED-FUNCTION in thread 
#<THREAD "main thread" RUNNING {100399C503}>: 
    The function COMMON-LISP-USER::FOO is undefined. 

宏擴展以固定點的方式施加:從代碼X0,計算X1爲X0的宏擴展開始,並繼續,直到沒有任何宏展開了。您的宏不會自行調用,宏擴展設施在每次傳遞後都會根據需要調用它(這也是爲什麼在宏擴展期間無法建立動態綁定的原因,除非手動調用macroexpand)。

雖然您可以將宏展開到也調用您的宏的代碼中。但是你必須小心,它並不是無條件的,否則你將會有無限的宏觀擴張。

如果list將是隻在運行時已知的值,則需要編寫常規函數。基本上,你想做的事:

(member symbol list :key #'first) 

MEMBERMACROEXPAND

1

遞歸宏是不可能的

想象一下,你嘗試過:

(defmacro expand (&rest elements) 
    (if (not (null (cdr elements))) 
     `(+ ,(car elements) ,(expand (cdr elements))) 
     (car elements))) 

現在,當宏功能正在編譯它擴展所有宏,以便它調用它(expand (cdr elements)) ..

(defmacro expand (&rest elements) 
    (if (not (null (cdr elements))) 
     `(+ ,(car elements) (+ (cdr elements) (+ (cdr elements) (+ (cdr elements) (+ (cdr elements) ...)))) 
     (car elements))) 

你看到了嗎?現在想象你,而不是僅僅擴大了第一部分,而不是遞歸,但留下一個更簡單的表達,而不是expand

(defmacro expand (&rest elements) 
    (if (not (null (cdr elements))) 
     `(+ ,(car elements) (expand ,@(cdr elements))) 
     (car elements))) 

這是完全不同的,因爲宏從不直接使用宏。然而(expand 1 2 3)擴展到(+ 1 (expand 2 3))和Lisp繼續,直到有沒有向左擴大宏,留下(+ 1 (+ 2 3))沒有遞歸

遞歸函數都ok的宏:

(defmacro expand (&rest elements) 
    (labels ((recfun (elements) 
      (if (not (null (cdr elements))) 
       `(+ ,(car elements) ,(recfun (cdr elements))) 
       (car elements)))) 
    (recfun elements))) 

它並不需要是一個本地定義的功能。通常我實現大多數的功能函數,使宏延遲的一些參數評測離開宏只是調用函數:

(defun make-env-fun (names) 
    (mapcar (lambda (name) (cons name (symbol-function name))) names)) 

(defmacro make-env (&rest variables) 
    `(make-env-fun ',variables)) 

(make-env cons car cdr) 
; ==> ((cons . #<system-function cons>) 
;  (car . #<system-function car>) 
;  (cdr . #<system-function cdr>)) 

所以宏存在,因爲我不想做(make-env 'cons 'car 'cdr)(make-env '(cons car cdr)) 。宏只解決了這個問題,而不是功能仍在做的實際工作。

因此,爲了與您的問題相關,您需要一個允許(already-exist symbol ((symbol "bla")))而不是(already-exist-fun 'symbol '((symbol "bla")))的宏。你看到了嗎?

+0

你是指',(展開(cdr elements))'或'(expand,(cdr elements))'?就我所知,前者的界定不明確;在SBCL上,擴展的調用首先被認爲是宏體中的一個函數。然後這個名字必然是一個宏(它給出了一個警告)。如果我們重新定義宏,現在它已經擴展了,但是最終的形式重新定義了宏。儘管如此,可能存在固定點,但通常「遞歸宏」是將*擴展成自己的調用的宏。 – coredump

+1

@coredump第一個代碼塊故意無效,因爲它首先是沒有意義的。任何解決方案都可能超出CLHS,可能還行,就像變異文字一樣。遞歸宏是不可能的。擴展爲使用同一個宏的代碼的宏並不是真正的遞歸,因爲宏只能執行一個步驟,因爲宏擴展器是遞歸的,所以它給了你一個遞歸的感覺。第三個模塊就是這樣一個例子,它可以'(展開,@(cdr elements))' – Sylwester