开发者

How do I apply "or" to a list in elisp

开发者 https://www.devze.com 2023-03-03 10:10 出处:网络
In elisp I can evaluate or as a function just like +. (or nil 0 nil) ==> 0 (+ 1 0 1) ==> 2 I can use apply to apply + to a list

In elisp I can evaluate or as a function just like +.

(or nil 0 nil) ==> 0

(+ 1 0 1) ==> 2

I can use apply to apply + to a list

(apply '+ '(1 开发者_JAVA百科0 1)) ==> 2

So, I would think or would work the same way, but it doesn't.

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

I imagine this comes from some internal magic used to implement the short-circuit evaluation. How can I use apply to execute the or operation over a list?


P.S. my desired application is to find out whether any elements on the command line match a particular pattern, so the important part of what I am writing is:

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

But it doesn't work


The problem is that or is a macro (which is the "internal magic" in question), and you're right that that's done so it can do short-circuiting. If or was a function, then calling it would need to follow the usual rules for evaluating a function call: all the arguments would need to get evaluated before the call is made.

See also this question -- it's about Scheme but it's the exact same issue.

As for a solution, you should probably use some, as in:

(some (lambda (x) (string-match-p "pattern" x)) command-line-args)

Note: this uses common lisp that is not included in emacs by default. Just use (require 'cl)


If it makes you feel any better, you're in good company! This is the third question in the "Common Pitfalls" section of the Lisp FAQ:

Here's the simple, but not necessarily satisfying, answer: AND and OR are macros, not functions; APPLY and FUNCALL can only be used to invoke functions, not macros and special operators.

...and Eli is of course right on the money with his suggestion to use SOME:

The Common Lisp functions EVERY and SOME can be used to get the functionality you intend when trying to apply #'AND and #'OR.

(The FAQ and this answer are mostly about Common Lisp but in this case if you omit the # character the answer is the same.)


If you don't care performance, use (eval (cons 'or '(nil 0 nil)))


When I was trying to 'apply' a macro to an argument list, I got an error that the function is unbound, which means that, 'apply' only receives a function, instead of a macro, as its first argument.

In order to fix this, I wrote a new function 'apply-macro' as follows:

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

For instance, I wrote a macro to concatenate multiple lists together:

(defmacro conc-lists (&rest lists)
  `(funcall #'concatenate 'list ,@lists))

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

Now try 'apply-macro':

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

It works and returns the same output.

In fact, it will be expanded into:

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

You can also pass a form to a macro:

(apply-macro 'conc-lists (maplist #'list '(a b c)))
;;=> ((A B C) (B C) (C))

Get back to your question, it's solved:

(apply-macro 'or '(nil 0 nil)) ;;=> 0


I'm only guessing here, but I think or might be one of these 20-odd 'functions' in lisp that are not really functions, since they don't always evaluate all parameters.

This makes sense to make or one of these, since if you have found one TRUE, you can stop searching. In other words, or is not a function as a form of optimization. Still only guessing though.


Eli Barzilay's answer is correct and idiomatic. I want to provide an alternative answer based on dash.el, the library I use to write terse functional-style code, when I have to work with lists. or returns the first non-nil element, nil otherwise, due to short-circuiting. Therefore simply use -first:

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

identity function simply returns its argument. Which is clever, because -first applies a predicate until it returns non-nil. identity returns non-nil if the argument is itself non-nil. If you simply want to test whether there is non-nil elements in a list, use -any? instead:

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

精彩评论

暂无评论...
验证码 换一张
取 消