2017-02-16 73 views
0

在表中執行按行操作時(實現爲列表列表),通過各自的列名引用單元格很方便。我決定寫一個快捷方式,這樣我就不用寫有沒有辦法判斷宏展開是否會看到詞法上下文?

(elt row 5) 

等,並能編寫

(:col "Relevant Header") 

代替。由於沒有與Common Lisp的很多經驗,我寫了一個宏

(defmacro with-named-columns! (sequence-of-rows-symbol body &key headers) 
    (labels 
     ((replace-col (form headers row-symbol) 
     (if (listp form) 
      (if (eql (car form) :col) 
       `(elt ,row-symbol ,(position (cadr form) headers :test #'equal)) 
       (mapcar #'(lambda (x) (replace-col x headers row-symbol)) form)) 
      form))) 
    (let ((row (gensym "ROW"))) 
     `(map 'list 
      (lambda (,row) ,(replace-col body headers row)) 
      ,sequence-of-rows-symbol)))) 

的是,雖然不完美,似乎功能不夠好:

(defparameter *table* '(("h1" "h2") ("foo" "bar") ("" "baz") ("foo" "qux"))) 

(defparameter *sequence-of-rows* (cdr *table*)) 

(with-named-columns! *sequence-of-rows* 
    (when (equal (:col "h1") "") 
    'do-nothing-but-make-a-note) 
    :headers ("h1" "h2")) 

=> (NIL DO-NOTHING-BUT-MAKE-A-NOTE NIL) 

它macroexpands我打算方式:

(macroexpand-1 
'(with-named-columns! *sequence-of-rows* 
    (when (equal (:col "h1") "") 
    'do-nothing-but-make-a-note) 
    :headers ("h1" "h2"))) 

=> 
(MAP 'LIST 
    (LAMBDA (#1=#:ROW724) 
     (WHEN (EQUAL (ELT #1# 0) "") 'DO-NOTHING-BUT-MAKE-A-NOTE)) 
    *SEQUENCE-OF-ROWS*) 

到目前爲止好。但是,標題通常以第一行的形式附加到數據上。對於這樣的情況,是很自然的有一個單獨的設施,

(defmacro process-rows (symbol-bound-to-table-with-exactly-one-header-row body) 
    (let ((symbol-for-sequence-of-rows (gensym "SEQUENCE-OF-ROWS"))) 
    `(let ((,symbol-for-sequence-of-rows (rest ,symbol-bound-to-table-with-exactly-one-header-row))) 
     (with-named-columns! ,symbol-for-sequence-of-rows 
      ,body 
      :headers ,(first (symbol-value symbol-bound-to-table-with-exactly-one-header-row)))))) 

這又似乎很好地工作:

(process-rows *table* 
    (when (equal (:col "h1") "") 
     'do-nothing-but-make-a-note)) 

=> (NIL DO-NOTHING-BUT-MAKE-A-NOTE NIL) 

宏擴展導致了一個讓表單內宏,似乎有與此

(macroexpand-1 
'(process-rows *table* 
    (when (equal (:col "h1") "") 
     'do-nothing-but-make-a-note))) 

=> 
(LET ((#1=#:SEQUENCE-OF-ROWS726 (REST *TABLE*))) 
    (WITH-NAMED-COLUMNS! #1# 
         (WHEN (EQUAL (:COL "h1") "") 
         'DO-NOTHING-BUT-MAKE-A-NOTE) 
         :HEADERS ("h1" "h2"))) 

旁註沒有問題:我們希望,這個宏擴展說明了爲什麼我認爲是有益的頭部提供明確的名單WITH-NAMED-COLUMNS!同時隱藏其餘的行在一個符號中。

然而

(let ((table '(("h1" "h2") ("foo" "bar") ("" "baz") ("foo" "qux")))) 
    (process-rows table 
    (when (equal (:col "h1") "") 
     'do-nothing-but-make-a-note))) 

調用調試器(SBCL)與消息「變量TABLE是未結合的。」 - 的PROCESS-ROWS的宏擴展期間。

我還不夠了解Common Lisp評估過程。看來PROCESS-ROWS看不到詞法變量,我聽說過這個問題。但我的其他宏

(let ((sequence-of-rows '(("foo" "bar") ("" "baz") ("foo" "qux")))) 
    (with-named-columns! sequence-of-rows 
    (when (equal (:col "h1") "") 
    'do-nothing-but-make-a-note) 
    :headers ("h1" "h2"))) 

=> (NIL DO-NOTHING-BUT-MAKE-A-NOTE NIL) 

LET的正文中評估正好。我沒有看到這兩個擴展之間的區別。有沒有辦法看到?另外,我寫了一個不同的宏(一個我打算用得最多的,而不是寫在頂層LET形式;作爲事實上,它是一個宏觀其中問題表現出來起初),這macroexpands到

(LET ((#1=#:TABLE727 (READ-CSV #2=#P"~/test.csv"))) 
    (WRITE-CSV 
    (PROGN 
    (PROCESS-ROWS #1# 
        (WHEN (EQUAL (:COL "h1") "") 
        'DO-NOTHING-BUT-MAKE-A-NOTE)) 
    #1#) 
    :STREAM #2#)) 

這標誌着在評估了同樣的錯誤:「變量#:TABLENNN是自由的。」我不解PROCESS-ROWS怎麼能不看變量,因爲這些#1=#1#是同一個地方(直接引用很像宏展開PROCESS-ROWS)。顯然,這並不能保證任何東西。

如果有辦法確保詞彙捕捉,我該如何做,如果沒有 - 我在哪一點進入了未指定行爲的地方?或者我可能錯過了一些簡單的東西?

+0

一般來說,一個短的重點問題會比一個漫長的問題得到更好的答案。你可能想考慮把你的問題分成幾個獨立的問題。 – sds

+0

我知道。標題中提出的問題可能有一個正確的答案。我有興趣解釋兩個宏觀展開中的差異,而不是解決最初的問題。如果有人能寫出一對更加簡潔的表格,表明與所討論的那對相同的行爲,那就更好了。 (我還不能。) – Akater

+0

我建議你自己試試我的方法(請參閱答案)。 – sds

回答

2

第一個錯誤

它使用類似宏的典型錯誤。只需將其寫入功能性風格 - 它會容易得多。

第二個錯誤

(defmacro with-named-columns! (sequence-of-rows-symbol body &key headers) 
    (labels 
     ((replace-col (form headers row-symbol) 
     (if (listp form) 
      (if (eql (car form) :col) 
       `(elt ,row-symbol ,(position (cadr form) headers :test #'equal)) 
       (mapcar #'(lambda (x) (replace-col x headers row-symbol)) form)) 
      form))) 
    (let ((row (gensym "ROW"))) 
     `(map 'list 
      (lambda (,row) ,(replace-col body headers row)) 
      ,sequence-of-rows-symbol)))) 

你不能只是走在代碼樹和替換類似的東西。您實施的樹步完全不尊重Lisp語法。我們需要一個代碼步行者,它實際上理解Lisp語法。或者使用類似macroletflet(由用戶sds提及)。

的錯誤:綁定可能不存在,所以你不能訪問它

(defmacro process-rows (symbol-bound-to-table-with-exactly-one-header-row body) 
    (let ((symbol-for-sequence-of-rows (gensym "SEQUENCE-OF-ROWS"))) 
    `(let ((,symbol-for-sequence-of-rows (rest ,symbol-bound-to-table-with-exactly-one-header-row))) 
     (with-named-columns! ,symbol-for-sequence-of-rows 
      ,body 
      :headers ,(first (symbol-value symbol-bound-to-table-with-exactly-one-header-row)))))) 

什麼是*table*符號值編譯時間?在宏擴展時間

什麼是table符號值編譯時間?在宏擴展時間?請注意,LET尚未執行,如果您編譯代碼並且symbol-value不能訪問不存在的詞法綁定。 symbol-value實際上根本無法訪問詞法綁定。

注:調用SYMBOL-VALUE由宏在宏展開時運行,因爲你在反引號形式的子表達式的前面有一個逗號。

在宏擴展期間,您不能或不應該使用使用運行時值。因此,當您編寫和使用宏時,您需要傳遞實際數據,以便在宏擴展時知道它。

備選方案:

  1. 宏擴展時間時數據是可用的,甚至在運行時
  2. 重寫它作爲一束內搭功能
  3. 評估的源代碼提供數據在運行時作爲參數的必要信息

摘要:詞法綁定是n不是符號的值。動態綁定可能不存在。

+0

謝謝。我的以下摘要是否正確? 1)我嘗試評估頂級「LET」表單; 2)SBCL立即試圖編譯它,因爲這是SBCL的工作原理; 3)在編譯之前,'PROCESS-ROWS'擴展; 4)它擴展爲空的詞法環境,不包含'TABLE'。我無法弄清楚CLHS會發生什麼。但CLtL2 5.1.2第2段意味着符號'TABLE'然後被用來引用動態變量,這就是我的情況(我確實檢查了這一點)。所以,那麼5)變量'TABLE'被發現沒有綁定; 6)發出相應的錯誤信號。 – Akater

+0

在宏中評估不同表單時,你有什麼建議嗎? – clintm

+0

@clintm:關於Paul Graham的Lisp對宏的處理很好。以PDF格式提供:http://www.paulgraham.com/onlisp.html或購買印刷版。經典。 –

2

您的具體問題

我覺得你的問題是一個比較常見的一種,它通常是通過使你的「低層次的宏觀」解決(with-named-columns!)爲功能返回宏觀擴張時從「高級宏」(process-rows)明確地調用它。參看ensure-generic-function vs defgeneric

您的實際問題

我想建議您嘗試使用macrolet or flet和實際定義局部函數或宏(例如,(h1)),而不是你的不是非常lispy語法(:col "h1")

相關問題