如何在 alist 中使用函数?

问题描述

我在 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")) 是一个列表字面量:引用一个列表结构意味着不评估该列表结构的任何部分。相反,使用 listcons 以通常的方式构造新的数据结构:

(list (cons (concat "a" "-" "b") "c"))

listcons 都是计算参数的函数。此处,(cons (concat "a" "-" "b") "c") 创建一对 ("a-b" . "c"),并在最后一个表达式的结果上调用 list 将其放入一个新列表:(("a-b" . "c"))

REPL 中的实验可能有助于澄清问题。鉴于:

(setq x 42)

当您在 REPL 中计算 x 时,结果为 42。列表文字 '(x) 的计算产生列表 (x),这可能不是我们想要的。但是,对 (list x) 的评估产生列表 (42)

另请注意,列表文字不能改变,即您不能对列表文字应用诸如 setcarnconc 之类的破坏性操作。也就是说,试图修改引用列表的程序具有未定义的行为。这种未定义的行为包括看起来正常工作,直到有一天出现可怕的错误。请参阅 2.9 Mutatability5.6 Modifying Existing List Structure。但是,如果需要,可以安全地更改通过 listcons 创建的列表。

使用反引号语法

这里也可以使用反引号语法,但我不推荐它。反引用或准引用提供了一种创建列表模板并控制评估的方法。在列表前面单独使用反引号而不是通常的引号字符,等效于引用列表。但是,您可以添加辅助字符以更好地控制结果;在列表元素前放置逗号意味着在创建列表之前将评估该元素。所以,你可以这样做:

`((,(concat "a" "-" "b") . "c"))

这里的表达式 (concat "a" "-" "b") 在列表模板的其余部分被转换为列表之前被计算。而且,您还可以以准报价的形式做很多其他的事情。例如,您可以将某种形式的求值列表结果拼接到拟引用形式的周围列表中。

很整洁。那么,我为什么不推荐这个解决方案呢?首先,使用它并没有提高代码的清晰度,实际上对许多人来说似乎不太清楚; quasiquote 形式的评估模型与常规函数调用中的评估模型不同,与 quote 形式中的评估模型不同。使用 listcons 的版本非常清晰,评估也很容易理解。

其次,准报价可能是微妙而复杂的;这类代码很容易出错,甚至有可能徘徊在 undefined behavior 中。

第三,这可能真的是第二点的扩展,listcons 版本更易于维护。假设引用的表达式已更改,以便引入变量 x 代替 "c"。 quasiquote 版本的维护者需要保持警惕:

`((,(concat "a" "-" "b") . x))

不会按预期工作;维护者需要记住添加一个逗号,以便在创建列表之前将 x 评估为其值:

`((,(concat "a" "-" "b") .,x))

通过使用 listcons 版本,将文字 "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"