DOLIST Pitfall

Shaka, when the walls fell

Common LISP expressions return a value,

    SBCL> 42

    42
    SBCL> (+ 3 2 1)

    6
    SBCL> (if (> 0.5 (random 1.0)) 'heads 'tails)

    HEADS
    SBCL> (let ((x 42)) x)

    42

with complications such as PROG1 which returns the first-form.

    SBCL> (prog1 99 640 42)

    99

This is handy to yield something and then modify that something.

    SBCL> (let ((x 0)) (defun counter () (prog1 x (incf x))))

    COUNTER
    SBCL> (counter)

    0
    SBCL> (counter)

    1

Many languages have to make do with a barbaric tmp = x; x++; return tmp dance. Gross!

DOLIST, the Weird

I've seen multiple people trip over the fact that DOLIST does not yield what one might reasonably expect.

    (dolist (x '(1 2 3))
      (format t "~&~a" x)
      x)

This returns NIL, not 3--surprise!

    SBCL> (dolist (x '(1 2 3)) (format t "~&~a" x) x)

    1
    2
    3
    NIL

The return value is the optional third argument following the list-form, here '(1 2 3). However, this cannot be the first argument.

    SBCL> (dolist (x '(1 2 3) x) (format t "~&~a" x))

    1
    2
    3
    NIL
    SBCL> (dolist (x '(1 2 3) 'i-want-x-here) (format t "~&~a" x))

    1
    2
    3
    I-WANT-X-HERE

LOOP, the Unloved

There is LOOP, but many folks seem hesitant to use it. Dunno why.

    SBCL> (loop for x in '(1 2 3) do (format t "~&~a" x) finally (return x))

    1
    2
    3
    3

Sometimes a list is being built up, which LOOP is good at, but again folks are often hesitant to use LOOP. Dunno why. Assume that '(1 2 3) is actually some complicated API call, if that makes more sense than using LOOP to make '(1 2 3) from '(1 2 3).

    SBCL> (loop for x in '(1 2 3) collect x)

    (1 2 3)

This can be done with DOLIST, but I would probably write it with LOOP. The LOOP is probably generating very similar code to what the following probably also generate.

    SBCL> (let (foo) (dolist (x '(1 2 3) (nreverse foo)) (push x foo)))

    (1 2 3)
    SBCL> (let (foo) (dolist (x '(1 2 3)) (push x foo)) (nreverse foo))

    (1 2 3)

PROG2

Given PROG1, guess what PROG2 does. (Common LISP suffers from having too many functions and not enough; there's bloat that probably could be shuffled off to a library or dropped, whoops about that backwards compatibility, and then other folks complain that HTTP (or even socket) support is not in base...)

On LOOP Avoidance

The peanut gallery indicates that LOOP is unloved as it uses a domain specific language and not SEXP--LISP purism. Another reason it is unloved is that some noobs look at LOOP and struggle with the "oh no, another thing to learn" problem.

But there is not much reason to ITERATE this more.

Another fun thing is to write your own looping macros...

tags #lisp

bphflog links

bphflog index

next: Code Review