在表中執行按行操作時(實現爲列表列表),通過各自的列名引用單元格很方便。我決定寫一個快捷方式,這樣我就不用寫有沒有辦法判斷宏展開是否會看到詞法上下文?
(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
)。顯然,這並不能保證任何東西。
如果有辦法確保詞彙捕捉,我該如何做,如果沒有 - 我在哪一點進入了未指定行爲的地方?或者我可能錯過了一些簡單的東西?
一般來說,一個短的重點問題會比一個漫長的問題得到更好的答案。你可能想考慮把你的問題分成幾個獨立的問題。 – sds
我知道。標題中提出的問題可能有一個正確的答案。我有興趣解釋兩個宏觀展開中的差異,而不是解決最初的問題。如果有人能寫出一對更加簡潔的表格,表明與所討論的那對相同的行爲,那就更好了。 (我還不能。) – Akater
我建議你自己試試我的方法(請參閱答案)。 – sds