有没有办法判断宏观扩张是否会看到词汇背景?

时间:2017-02-16 23:02:42

标签: macros common-lisp

在表中执行逐行操作(实现为列表列表)时,可以通过各自的列名称来引用单元格。我决定写一个快捷方式,这样我就不用写了

(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)

它宏观扩展了我的意图:

(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)

Macroexpansion导致let表单中的宏,并且这个

似乎没有问题
(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形式;事实上,它是最初在问题中表现出来的一个宏),宏观扩展到

(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的宏展开)。显然,这并不保证任何事情。

如果有办法确保词汇捕获,我该怎么做,如果没有 - 我在哪个阶段进入了未指明行为的土地?或者也许我错过了一些简单的东西?

2 个答案:

答案 0 :(得分:2)

您的具体问题

我认为您的问题是一个相对常见的问题,通常可以通过将“低级宏”(with-named-columns!)转换为函数来解决宏观扩展问题。从“高级宏”(process-rows)明确调用它时。参看ensure-generic-function vs defgeneric

您的实际问题

我建议您尝试使用macrolet or flet并实际定义本地函数或宏(例如(h1))而不是非常非常lispy语法(:col "h1")

答案 1 :(得分:2)

第一个错误

使用像这样的宏是一个典型的错误。只需将其写成功能风格 - 它就会容易得多。

第二个错误

window.onerror

您无法遍历代码树并替换类似的内容。您实现的树步骤完全不尊重Lisp语法。人们需要一个代码walker,它实际上理解Lisp语法。或者使用类似try { } catch (error) { const e = new ErrorEvent('error', {message:'my error', error:error}) window.dispatchEvent(e) } (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)))) 的内容(由用户sds提及)。

错误:绑定可能不存在,因此您无法访问

macrolet

编译时<{em>}的flet符号值是什么?在宏扩展时间

编译时<{em>}的(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的调用由宏在宏扩展时间运行,因为在反引号表单的子表达式前面有一个逗号。

在宏扩展期间,您根本不能或不应该使用使用运行时值。因此,当您编写和使用宏时,您需要传递实际数据,以便在宏扩展时知道它。

备选方案:

  1. 宏扩展时间
  2. 提供数据
  3. 在数据可用时评估源代码,甚至可以在运行时评估
  4. 将其重写为一系列函数,这些函数在运行时将必要的信息作为参数
  5. 摘要:词法绑定不是符号的值。动态绑定可能不存在。