I don’t like the CL Loop macro. I’m not alone on that; Paul Graham isn’t a fan either. On the other hand, Peter Seibel has a more positive view of them. That two accomplished and intelligent Lispers can disagree on the matter indicates that neither view is right or wrong, merely a matter of taste.
My two main objections to them are
- It’s not lisp
If I wanted to write in TCL or AppleScript or something, then I’d write in that language instead of Lisp. I really like Lisp s-expressions and see no reason to go out of my way to avoid them. - There’s no real specification
The loop facility is almost always documented by examples. As Graham says, its specification, to the extent that it has one, is the implementation. All this means that they are hard to understand and apply to situations that aren’t one of the patterns covered by the examples.
Loop partisans often claim that writing iteration is more concise with the loop macro and this is sometimes true (although I would argue that the code is less clear) but not always. Here’s a case in point. We’re offered some loop macro code that checks if any Emacs buffer file name contains the word projects
. This was intended to replace a long and complicated function that the poster had seen on another blog. When I first saw this, lots of different ways to do it in normal Lisp popped into my head and this post was originally going to be about how we could write a function to make this check in regular Lisp and still be just as concise.
But then I followed the link back to the original post and saw this elegant solution in the comments
(some (apply-partially 'string-match "projects") (mapcar 'buffer-name (buffer-list)))
That truly is a thing of beauty1. The iteration is implicit in the some
function and the apply-partially
makes a lambda
construct unnecessary.
The some
function is standard in CL but not in Scheme. Because it is handy and often just what you need, I thought I’d implement it in Scheme and add it to my standard library.
;; Return #t if pred? is true for at least one ;; member of seq, #f otherwise. (define some (lambda (pred? seq) (cond ((null? seq) #f) ((pred? (car seq)) #t) (else (some pred? (cdr seq))))))
As you can see, it’s trivial to implement.
The apply-partially
function is more interesting. It’s like
(f a1 a2 ... an)
except the first few arguments are fixed. What happens is that
(apply-partially f a1 a2 ... ak)
returns a new function that accepts the remaining (non-fixed) arguments and then applies f to all the arguments. Thus,
(apply-partially 'string-match "projects")
returns a function that checks if its argument contains the word projects
.
Strangely enough, it’s harder to explain than to implement:
;; Make a new function that applies f to ;; the x arguments and its input (define apply-partially (lambda (f . x) (lambda y (apply f (append x y)))))
Footnotes:
1 The original poster points out that the above code checks the buffer’s name not the name of the file associated with the buffer. The commenter responds with another version, which is a bit more complicated but still elegant and much nicer than the loop solution.