如何在elisp中对列表应用“or”?

23

在elisp中,我可以像使用+一样将or作为一个函数进行求值。

(or nil 0 nil) ==> 0

(+ 1 0 1) ==> 2

我可以使用 apply 函数将 + 应用于一个列表

(apply '+ '(1 0 1)) ==> 2

所以,我认为或者会以相同的方式工作,但实际上并不是这样。

(apply 'or '(nil 0 nil)) ==> error: (invalid-function or)

我想这来自于一些内部魔法,用于实现短路求值。如何使用apply在列表上执行或操作?


附言:我的期望应用是查找命令行中是否有任何元素与特定模式匹配,因此我正在编写的部分非常重要:

(apply 'or (mapcar (lambda (x) (string-match-p "pattern" x)) command-line-args))

但它不起作用

6个回答

21
问题在于or是一个宏(这就是所谓的“内部魔法”),你说得对,这样做可以进行短路。如果 or 是一个函数,则调用它需要遵循评估函数调用的通常规则:在调用之前需要评估所有参数。
参见此问题 - 它是关于Scheme的,但问题完全相同。
至于解决方案,您应该使用 some ,例如:
(some (lambda (x) (string-match-p "pattern" x)) command-line-args)

注意:这使用的是Emacs默认未包含的Common Lisp。只需使用(require 'cl)


1
+1 是给你提供了一个解决方案的,这是我完全忘记了的东西。 - Gustav Larsson
某些解决方案无法执行。我收到错误消息(void-function some),这意味着“some”未定义。 - Eponymous
同名:some 在 "cl-extra.el" 中被定义 - 你可能需要先加载它。或者你可能正在使用一个较旧的 Emacs 版本。 - Ken
是的,你需要要求它,因为 CL 的东西不是默认加载的。只需使用 (require 'cl) - Eli Barzilay
是的,根据“some”的文档字符串,这是最近的更改。 - Eli Barzilay
显示剩余2条评论

10
如果这让你感觉好一些的话,你并不孤单!这是Lisp FAQ中"常见陷阱"部分的第三个问题

这里是简单但未必令人满意的答案:AND和OR是宏而不是函数;APPLY和FUNCALL只能用于调用函数而非宏和特殊运算符。

...当然,Eli提出使用SOME的建议是正确的:

Common Lisp函数EVERY和SOME可以用于获取您尝试应用#'AND和#'OR时所需的功能。

(虽然FAQ和此答案大多关于Common Lisp,但在这种情况下,如果省略#字符,则答案相同。)


非常奇怪,因为(or)是在C源代码中定义的特殊形式,而不是实际的宏。("special form"是它不能与(apply)一起使用的原因吗?) - ocodo

3
如果您不关心性能,请使用(eval (cons 'or '(nil 0 nil)))

这个回答应该得到更多的赞,但我猜人们已经被训练成害怕eval了。 - Thomas

2

当我试图将宏应用到参数列表时,出现了一个错误,提示函数未绑定,这意味着'apply'只能接收一个函数作为其第一个参数,而不能是一个宏。

为了解决这个问题,我编写了一个新的函数'apply-macro',代码如下:

(defun apply-macro (macro arg-list)
  (eval
   `(,macro ,@(loop for arg in arg-list
                 collect `(quote ,arg)))))

例如,我编写了一个宏来将多个列表连接在一起:
(defmacro conc-lists (&rest lists)
  `(funcall #'concatenate 'list ,@lists))

例如: (conc-lists'(a b)'(c d)'(e f)) ;;=> (A B C D E F)

现在尝试使用'apply-macro':

(apply-macro 'conc-lists '((a b) (c d) (e f)))

它能够正常工作并返回相同的输出。

事实上,它将被扩展为:

(eval
   (conc-lists (quote (a b)) (quote (c d)) (quote (e f))))

您可以将表单传递给宏:
(apply-macro 'conc-lists (maplist #'list '(a b c)))
;;=> ((A B C) (B C) (C))

回到你的问题,已解决:
(apply-macro 'or '(nil 0 nil)) ;;=> 0

0

我只是猜测,但我认为or可能是Lisp中这20多个“函数”之一,它们并不真正是函数,因为它们并不总是评估所有参数。

这使得将or作为其中之一是有意义的,因为如果您已经找到一个TRUE,您可以停止搜索。换句话说,or不是一种优化形式的函数。尽管如此,我仍然只是在猜测。


我在思考特殊运算符,它们既不是宏也不是函数。我听说有25个这样的运算符(至少在clisp中)。除此之外,我对它们并不了解太多,但我很确定我犯了一个错误,说“or”是其中之一。 - Gustav Larsson
严格来说,特殊形式和宏之间存在差异——前者是语言的一部分,而后者不是。在ELisp的情况下,“or”恰好被定义为原始内置,但这只是一个实现问题,因为它也可以使用“if”定义为宏。(而ELisp并没有试图达到某种极简主义,否则它本可以这样做。) - Eli Barzilay

0

Eli Barzilay的回答是正确和惯用的。我想提供一个基于dash.el的替代答案,这是我在处理列表时使用的库,以编写简洁的functional-style代码。or由于短路而返回第一个非nil元素,否则返回nil。因此,只需使用-first

(-first 'identity '(nil 0 1 nil)) ; 0
(-first 'identity '(nil nil)) ; nil

identity 函数简单地返回它的参数。这很聪明,因为 -first 应用一个谓词直到它返回非空值。如果参数本身不为空,则 identity 返回非空值。如果您只想测试列表中是否存在非空元素,请改用 -any?

(-any? 'identity '(nil 0 1 nil)) ; t
(-any? 'identity '(nil nil)) ; nil

1
在这个上下文中,还有 (-flatten <list>) - ocodo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接