6

I expected the outcome of the following small program:

(progn
  (setq x 1)
  (case x
    (x 'Yes)
    (t 'No)))

to be Yes, given that case compares things with eql, and surely x is eql to itself. However the outcome is No and I am completely puzzzled by this!



This is a long comment, not an answer!

Thanks to everybody for making everything perfectly clear. My original intention was to use 'case' to simulate something like

(cond
  ((equal x a) 'this)
  ((equal x b) 'that)
  ((equal x c) 'something-else))

which would look much cleaner if it were equivalent to

(case x
  (a 'this)
  (b 'that)
  (c 'something-else))

But, alas, I understand that these are not equivalent forms due to the fact that case evaluates x but does not evaluates the keys in each clause.

I thought of faking things, such as in

(let ( (x 2) (a 1) (b 2) (c 3) ) (flet ( (eql (x y) (equal x (eval y))) )
  (case x
    (a 'this)
    (b 'that)
    (c 'something-else))
))

which does work as I'd like but I suppose it is a bit blasphemous to alter the meaning of something as basic as 'eql', even if only temporarily!

Drew
  • 75,699
  • 9
  • 109
  • 225
Ruy
  • 787
  • 4
  • 11
  • 1
    FYI it is recommended to use `cl-case` from `cl-lib.el` instead of `case` from `cl.el`. The behaviour you see is not a bug; it is a feature of elisp macros. As the docstring of `cl-case` describes, the first occurence of `x` is evaluated as an expression, but the second occurence of `x` stands for the symbol `'x`, i.e. the comparison being made is `(eql x 'x)`, which returns `nil`. – Basil Oct 26 '17 at 19:42
  • If you are interested in reading more about this, see [`(cl) Conditionals`](https://www.gnu.org/software/emacs/manual/html_node/cl/Conditionals.html) and [`(elisp) Macros`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Macros.html). See [`(elisp) nil and t`](https://www.gnu.org/software/emacs/manual/html_node/elisp/nil-and-t.html) for why those two symbols are seemingly handled differently in `cl-case`. – Basil Oct 26 '17 at 19:45
  • @Basil, thanks for your answer. However, I did not find the explanation you gave in the docstring. Anyways, this sounds really strange to me! – Ruy Oct 26 '17 at 20:13
  • @Basil, do you know an elisp function which looks like cl-case, but which makes the comparison using *equal* and which evaluates things in the same way, i.e, without this weird interpretation of symbols? I know one could use *cond*, but I feel a *case* function is sometimes better. – Ruy Oct 26 '17 at 20:26
  • See the excellent answers so far. In particular refer to [Stefan's answer](https://emacs.stackexchange.com/a/36428/15748) for an alternative to `cl-case` and `cond`. – Basil Oct 26 '17 at 23:35

3 Answers3

11
  1. case is an alias for cl-case, so there is no difference, there.

  2. This aspect of the behavior is the same in Common Lisp.

  3. There is nothing very weird here. You just need to read the doc, which tells you that the first arg to case is evaluated and the result is compared with the car entries of the following lists. cl-case is a macro, so in general (i.e., unless told otherwise) you can suppose that it does not evaluate its arguments.

  4. But perhaps the doc should explicitly point out that nothing in those lists is evaluated - only the first argument is evaluated.

  5. More generally wrt the doc: This is an emulation of case in Common Lisp. The doc for case in Common Lisp itself is very clear: either the hyperspec or (especially) Common Lisp the Language.

  6. Unfortunately nearly ALL of the Emacs emulation of Common-Lisp stuff is poorly documented. One really has to check the Common-Lisp doc to understand what it is that Emacs is trying to emulate - and then guess the ways in which it doesn't succeed in emulating this or that bit.

  7. Perhaps this is really what you were trying to do (just a guess):

    (case x
      (1 'Yes)
      (t 'No))
    
Drew
  • 75,699
  • 9
  • 109
  • 225
  • I don't know which version of the docstring you were looking at, but I don't find the one I have in front of me (Emacs 24.4) clear about the fact that the key is not evaluated. “EXPR is evaluated and compared against each key in each KEYLIST (…). A single atom may be used in place of a KEYLIST of one atom.” – Gilles 'SO- stop being evil' Oct 26 '17 at 21:53
  • To clarify: In my comment I did not mean `nil` and `t` are handled differently by `case`, but rather that they are special symbols which evaluate to themselves. – Basil Oct 26 '17 at 23:30
  • 1
    In addition, I did not suggest `case` and `cl-case` behave differently (though this is not impossible across Emacs versions); rather that the `cl-` namespace is preferred over `cl.el` in new elisp. – Basil Oct 26 '17 at 23:38
  • Great answer, thanks! :) Alas comments can't be edited for clarity. – Basil Oct 27 '17 at 00:25
  • @Basil: Comments can be deleted. New comments can replace them, if need be. Delete + insert = replace = edit, sort of. – Drew Oct 27 '17 at 00:28
  • Good point. If you think I should delete them, I will; though I'd rather not for continuity's sake. – Basil Oct 27 '17 at 00:31
  • No, I wasn't suggesting anything; just mentioning that workaround to editing (but it does change the order). (Comments can really be edited, of course, but only for a few minutes.) – Drew Oct 27 '17 at 04:54
8

BTW, I recommend the use of pcase, which is more powerful. The equivalent code to what you wrote, would be:

(let ((x 1))
  (pcase x
    ('x 'Yes)
    (_ 'No)))

Making it clear that x is not evaluated. This said, it would probably confuse you as well, because if you naively write:

(let ((x 1))
  (pcase x
    (x 'Yes)
    (_ 'No)))

it will tell you Yes but for the wrong reason: the x pattern matches anything (not just the value held in variable x) and it then re-binds a new variable x, so

(let ((x 1))
  (pcase 5
    (x x)
    (_ 'No)))

would return 5. But with pcase you can do the test you want, which is written in a significantly more verbose way:

(let ((x 1))
  (pcase x
    ((pred (eql x)) 'Yes)
    (_ 'No)))
Stefan
  • 26,154
  • 3
  • 46
  • 84
4

The first part of each clause in a case form is a value (or a list of values), not an expression. The x before 'Yes is syntactically interpreted as a value. The x after case, on the other hand, is interpreted as an expression, i.e. it is evaluated; its value is 1. The value 1 (which is an integer) is not equal to the value x (which is a symbol). Contrast:

  • (case x (x 'Yes) (t 'No))No   (1x)
  • (case 'x (x 'Yes) (t 'No))Yes   (x = x)
  • (case x (1 'Yes) (t 'No))Yes   (1 = 1)

case is meant to be similar to case statements in most other languages, which compares the value of an expression against some constants. Each clause of a case statement specifies a constant (i.e. something that is already in the form of a value in the source code) to compare against the value of the expression.

(case (+ x y)
  (1 "one")
  (2 "two")
  ((3 4) "many")    ; instead of a single value, a clause can have a list of values
  (t "lots"))

If you want to test whether the value of an expression is equal to the value of one of several expressions, then you're doing something more complicated than what case can offer. Using cond is perfectly reasonable.

https://www.gnu.org/software/emacs/manual/html_node/cl/Conditionals.html#index-cl_002dcase-32