5

I'm trying to wrap my head around Elisp's cl-loop facility but can't seem to find a way to skip elements. Here's an artificial example to illustrate the problem: I'd like to loop over a list of integers and get a new list in which all odd integers from the original list are squared. The even integers should be omitted.

According to the documentation of cl-loop, I should be able to do this:

(loop for i in '(1 2 3)
      if (evenp i)
        append (list)
      else
        for x = (* x x)
        and append (list x))

The desired output is '(1 9) instead I get an error:

cl--parse-loop-clause: Expected a `for' preposition, found (list x)

Apparently the and doesn't work as expected but I don't understand why. (I'm aware that I could simplify the else block to consist of only one clause such that the and isn't needed anymore. However, I'm interested in situations where you really have to connect several clauses with and.)

Second part of the question: Ideally, I would be able to write this:

(loop for i in '(1 2 3)
      if (evenp i)
        continue
      for x = (* x x)
      append (list x))

Continue is a very common way to skip iterations in other languages. Why doesn't cl-loop have a continue operator? Is there a simple way to skip elements that I overlooked (simpler than what I tried in the first example)?

tmalsburg
  • 354
  • 2
  • 11

4 Answers4

9

In Common Lisp it is not possible to write such a LOOP. See the LOOP Syntax.

There is a set of variable clauses on the top. But you can't use one like FOR later in the main clause. So in an IF clause you can't use FOR. If you want to introduce a local variable, then you need to introduce it at the top as a WITH clause and set it later in the body.

(loop for i in '(1 2 3)
      with x
      if (evenp i)
        append (list)
      else
        do (setf x (* i i))
        and append (list x))

LOOP in Common Lisp also has no continue feature. One would use a conditional clause.

Note, that Common Lisp has a more advanced iteration construct as a library ITERATE. It does not exist for Emacs Lisp, though.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • Excellent answer, thank you. It's strange that cl-loop is so powerful and yet makes you jump through hoops when you want something simple like continue. – tmalsburg Dec 06 '13 at 13:54
  • @tmalsburg `LOOP` serves as an example both for and against the virtues of Lisp macros. – Aaron Miller Dec 06 '13 at 20:21
  • @Rainer -- Looking forward to the day [`iterate`](http://common-lisp.net/project/iterate/doc/Don_0027t-Loop-Iterate.html) is added to Emacs Lisp. – Drew Dec 08 '13 at 02:58
  • @AaronMiller -- are you suggesting that implementing continue would be impossible or at least difficult in the context of the loop macro? – tmalsburg Dec 08 '13 at 12:44
  • @tmalsburg Not at all; what I'm saying is that, while `LOOP` is an extremely powerful and flexible facility of the language, to the extent that it has [its own chapter](http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node235.html) in *Common Lisp: The Language*, it is also an extremely subtle and complex topic requiring great mastery to understand in depth, to the extent that it *needs* its own chapter in *Common Lisp: The Language*. – Aaron Miller Dec 08 '13 at 17:20
4

You could do:

(loop for i in '(1 2 3)
      if (oddp i) collect (* i i))

That would solve your sample problem.

Tobias
  • 5,038
  • 1
  • 18
  • 39
  • Sure, that solves it. But as I said, it was an artificial example and I wanted to understand the use of `and`. Thanks for the response anyway. – tmalsburg Dec 06 '13 at 13:59
3

And here's another without loop (yes, I know you asked for loop):

(let ((ns  ()))
  (dolist (n  '(1 2 3))
    (when (oddp n) (push (* n n) ns)))
  (nreverse ns))

And without even cl-lib (which defines oddp):

(let ((ns  ()))
  (dolist (n  '(1 2 3))
    (unless (zerop (mod n 2)) (push (* n n) ns)))
  (nreverse ns))

Everything about such definitions is clear -- just Lisp. Same with @abo-abo's examples.

loop is a separate language. Its purpose is to express common iteration scenarios, and for that it can do a good job. But Lisp it is not. ;-) It is a domain-specific language for expressing iteration. And it lets you make use of Lisp sexps, fortunately.

(Think of the Unix find command -- similar. It's very handy, but it's another language unto itself.)

[No flames, please. Yes, I know that dolist and all the rest are essentially no different from loop -- neither more nor less Lisp. But they are lispier than loop. Almost anything is lispier than loop.]

Drew
  • 29,895
  • 7
  • 74
  • 104
  • thanks for the explanation. I was surprised to find loop for the reasons that you mention. It doesn't seem idiomatic. I'm writing a source for emacs-helm and since most other sources use cl-loop I decided to follow that for consistency. – tmalsburg Dec 08 '13 at 12:49
  • @tmalsburg: Some people are great fans of `loop`; others are not. Those who use it a lot speak its language fluently and it feels natural to them. – Drew Aug 04 '14 at 13:54
2

Here's a loop solution:

(loop for i in '(1 2 3)
   when (oddp i) collect (* i i))

Here's a functional solution:

(delq nil
      (mapcar (lambda(x) (and (oddp x) (* x x)))
              '(1 2 3)))

Here's a slightly different solution (be careful with mapcan - it's destructive):

(mapcan (lambda(x) (and (oddp x) (list (* x x))))
        '(1 2 3)) 
abo-abo
  • 20,038
  • 3
  • 50
  • 71