23

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 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

Eponymous
  • 6,143
  • 4
  • 43
  • 43

6 Answers6

21

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)

Community
  • 1
  • 1
Eli Barzilay
  • 29,301
  • 3
  • 67
  • 110
10

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.)

Ken
  • 486
  • 3
  • 11
  • Very odd, considering `(or) is a special form defined in C source code.` as opposed to an actual macro. (Is _special form_ the reason it doesn't work with `(apply)`?) – ocodo Aug 30 '22 at 09:23
3

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

netawater
  • 15,214
  • 4
  • 24
  • 21
  • This answer deserves way more upvotes, but I guess people have been trained to be [afraid of eval](https://stackoverflow.com/questions/2571401/why-exactly-is-eval-evil). – Thomas Oct 23 '17 at 12:19
2

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
Michael He
  • 31
  • 2
0

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
Community
  • 1
  • 1
Mirzhan Irkegulov
  • 17,660
  • 12
  • 105
  • 166
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.

Gustav Larsson
  • 8,199
  • 3
  • 31
  • 51
  • I was thinking of special operators, which are neither macros nor functions. I heard there are 25 of them (at least in clisp). I don't know much more about them than that, but I'm pretty sure I made a mistake saying `or` is one of them. – Gustav Larsson May 06 '11 at 00:36
  • Well, strictly speaking, there is a difference between special forms and macros -- the former are part of the language, and the latter are not. In the ELisp case, `or` happens to have been defined as a primitive builtin, but that's just an implementation issue since it could just as well have been defined as a macro using `if`. (And ELisp doesn't try to get to some minimalism, or it would have done just that.) – Eli Barzilay May 06 '11 at 06:06