Monday, June 2, 2008

Loop the Loop

Along with the fancier CLOS features, and format, one of the things I found most jarring when I first learned Common Lisp was the loop macro. In a world of brackets, it's a strange creature; a small domain specific language for looping and iteration.

Like, funnily enough, CLOS (but not format; I still hate it), loop is one of the CL features I am now most addicted to, and miss the most when using other languages. It was apparently only added to the standard more or less at the last minute, and replaces (but is backwards compatible with) a much simpler loop. I'd be fascinated to learn just how it came about, but the Internet is unhelpful on the subject.

What's loop like? Well, you can iterate over lists, vectors, and hash-tables (with destructuring of elements, if you like), use multiple for-loop like counters, assign variables, do things conditionally, return early, have initial and finally operations, and gather results into lists. It really is a little mini-language.

The syntax for hash tables is particularly weird: (loop for var being the hash-keys/hash-values in hash-or-package ...). I mean, why? Maybe it was to discourage people from doing this, when it comes to it, rather weird operation. :)

Collecting is my personal favourite; with loops within loops, you can turn nasty things like XML into nice things like lists of nested CLOS objects. In particular, using loop seems nearly always a bit nicer than using mapcar;

(loop for i in *alist*
collect (princ-to-string i))

vs

(mapcar (lambda (x) (princ-to-string x)) *alist*)

A matter of taste, I suppose, but there you are.

Ultimately, I believe, loops compile into big scary blocks for do statements; do statements being generally considered too horrible for direct use.

I suspect that the list comprehensions you see in Python and such were either inspired by loop, or vice versa. They're not even vaguely as capable, of course.

1 comment:

  1. Not a great MAPCAR example, because you could do it with:

    (mapcar 'princ-to-string *alist*)

    ReplyDelete