如何在列表中使用函数?

How to use functions inside an 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)?

您可以使用 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"

使用 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 Mutatability and 5.6 Modifying Existing List Structure。但是如果需要,可以安全地改变通过 listcons 创建的列表。

使用反引号语法

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

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

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

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

其次,准引用可能微妙而复杂;这种代码很容易出bug,甚至有可能会跑到.

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

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

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

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

通过使用 listcons 版本,将文字 "c" 更改为变量 x 即可:

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

一般来说,准引用非常适合编写宏,对于更复杂的列表表达式也能得心应手;激增的 list 表达式会变得非常混乱(这正是在没有准引用的情况下尝试使用宏时发生的情况)。对于像这里这样的简单问题,使用准引用确实不值得权衡取舍。