问题描述
我在 alist 中使用了一个函数 (concat
),如下所示:
(setq org-dir-to-image-dir-alist
'(((concat "a" "-" "b") . "c")))
(dolist (cell org-dir-to-image-dir-alist)
(print (car cell)))
结果是:
(concat "a" "-" "b")
这不是我想要的。 concat
函数似乎没有被评估。为什么?以及如何对其进行评估(即输出 a-b
)?
解决方法
使用 list
创建新列表
'(((concat "a" "-" "b") . "c"))
是一个列表字面量:引用一个列表结构意味着不评估该列表结构的任何部分。相反,使用 list
和 cons
以通常的方式构造新的数据结构:
(list (cons (concat "a" "-" "b") "c"))
list
和 cons
都是计算参数的函数。此处,(cons (concat "a" "-" "b") "c")
创建一对 ("a-b" . "c")
,并在最后一个表达式的结果上调用 list
将其放入一个新列表:(("a-b" . "c"))
。
REPL 中的实验可能有助于澄清问题。鉴于:
(setq x 42)
当您在 REPL 中计算 x
时,结果为 42
。列表文字 '(x)
的计算产生列表 (x)
,这可能不是我们想要的。但是,对 (list x)
的评估产生列表 (42)
。
另请注意,列表文字不能改变,即您不能对列表文字应用诸如 setcar
或 nconc
之类的破坏性操作。也就是说,试图修改引用列表的程序具有未定义的行为。这种未定义的行为包括看起来正常工作,直到有一天出现可怕的错误。请参阅 2.9 Mutatability 和 5.6 Modifying Existing List Structure。但是,如果需要,可以安全地更改通过 list
或 cons
创建的列表。
使用反引号语法
这里也可以使用反引号语法,但我不推荐它。反引用或准引用提供了一种创建列表模板并控制评估的方法。在列表前面单独使用反引号而不是通常的引号字符,等效于引用列表。但是,您可以添加辅助字符以更好地控制结果;在列表元素前放置逗号意味着在创建列表之前将评估该元素。所以,你可以这样做:
`((,(concat "a" "-" "b") . "c"))
这里的表达式 (concat "a" "-" "b")
在列表模板的其余部分被转换为列表之前被计算。而且,您还可以以准报价的形式做很多其他的事情。例如,您可以将某种形式的求值列表结果拼接到拟引用形式的周围列表中。
很整洁。那么,我为什么不推荐这个解决方案呢?首先,使用它并没有提高代码的清晰度,实际上对许多人来说似乎不太清楚; quasiquote 形式的评估模型与常规函数调用中的评估模型不同,与 quote
形式中的评估模型不同。使用 list
和 cons
的版本非常清晰,评估也很容易理解。
其次,准报价可能是微妙而复杂的;这类代码很容易出错,甚至有可能徘徊在 undefined behavior 中。
第三,这可能真的是第二点的扩展,list
和cons
版本更易于维护。假设引用的表达式已更改,以便引入变量 x
代替 "c"
。 quasiquote 版本的维护者需要保持警惕:
`((,(concat "a" "-" "b") . x))
不会按预期工作;维护者需要记住添加一个逗号,以便在创建列表之前将 x
评估为其值:
`((,(concat "a" "-" "b") .,x))
通过使用 list
和 cons
版本,将文字 "c"
更改为变量 x
就可以了:
(list (cons (concat "a" "-" "b") x))
一般来说,quasiquotation 非常适合编写宏,对于更复杂的列表表达式也很方便;激增的 list
表达式可能会变得非常混乱(这正是在没有准引用的情况下尝试宏时发生的情况)。对于像这里这样的简单问题,使用 quasiquotation 确实不值得权衡。
您可以使用 backquote 和逗号来评估引号内的表达式:
(setq org-dir-to-image-dir-alist
`((,(concat "a" "-" "b") . "c")))
(dolist (cell org-dir-to-image-dir-alist)
(print (car cell)))
=> "a-b"