2015-10-06 51 views
1

查看Practical Common Lisp,我們正在尋找一個簡單的自動化單元測試框架。我們試圖寫一個宏來這樣使用:將循環改寫爲mapcar

(check (= (+ 1 2) 3) (= (- 1 4) 9)) 

這應該擴大到使用先前定義的函數report-result東西。建議的實施方式是:

(defmacro check (&body forms) 
    `(progn 
    ,@(loop for f in forms collect `(report-result ,f ',f)))) 

但是,擴展對我來說似乎相當程序化。我想用mapcar更換loop擴大到這樣的事情:

(mapcar #'(lambda (form) (report-result form 'form)) (list form-1 ... form-n)) 

不過,我明顯缺乏宏觀的寫作技巧來做到這一點。有人能想出一個這樣的宏嗎?

如果它是相關的,這是report-result定義:

(defun report-result (result form) 
    (format t "~:[FAIL~;pass~] ... ~a~%" result form)) 
+1

目前還不清楚你在問什麼。你想做什麼?你想在宏中用'MAPCAR'代替'LOOP'還是應該擴展的代碼包含一個'MAPCAR'? –

+0

好吧,說實話,我沒有想到這兩種形式都是可能的。 @ amalloy的答案爲前者提供了一個可能的解決方案。我最初試圖寫一些東西,以便在擴展代碼中出現'mapcar'。這也是可能的嗎? – Guilherme

回答

3

這的確是相當簡單:您只需將collect表達到您的mapcar的身體:

(defmacro check (&body forms) 
    `(progn 
    ,@(mapcar #'(lambda (form) 
        `(report-result ,form ',form)) 
       forms))) 

你不真的不需要知道任何關於正在發生的「宏觀」事情,爲了做你想要的替換,這只是用一些其他等效表達式替換loop :它在宏觀背景下的工作方式與在外部一樣。

如果你想擴大到mapcar你可以,但沒有真正的理由這樣做,因爲列表的大小是在編譯時已知的。下面是什麼樣子:

(defmacro check (&body forms) 
    `(let ((results (list ,@(mapcar #'(lambda (form) 
             `(list ,form ',form)) 
            forms)))) 
    (mapcar #'(lambda (result) 
       (report-result (car result) (cadr result))) 
      results))) 

它擴展,像這樣

> (macroexpand-1 '(check (+ 1 2) (* 2 3))) 
(let ((results (list (list (+ 1 2) '(+ 1 2)) 
        (list (* 2 3) '(* 2 3))))) 
    (mapcar #'(lambda (result) (report-result (car result) (cadr result))) 
      results)) 

正如你可以看到的是比較尷尬:宏觀已經有可以利用的形式像(+ 1 2),但爲了將它們保存到mapcar lambda的運行時查看,您必須發出兩次輸入表單。而且你必須產生整個列表來映射,而不是僅僅產生一個「完成」的列表來開始。此外,這會產生一個列表作爲輸出,並且需要一次將所有輸入和輸出存儲在內存中:原始宏與progn一次產生一個輸入和輸出,並在完成時丟棄它們。

+0

的確很簡單!我正在嘗試寫一些可以擴展到'mapcar'的東西,並且在轉義/轉義列表時遇到了問題。你現在不會有任何這樣的解決方案,是嗎? :) – Guilherme

+1

你不能這樣做(至少,沒有用)。沒有必要擴展到(mapcar f(list ...))而不是(list(f a)(f b))。由於列表在編譯時必須是已知的固定大小,因此在運行時而不是編譯時需要映射它。但是,我想我可以加入一個「無用」的版本來說明我的意思:我馬上編輯答案。 – amalloy