Lisp Macro Example, Continued
A while back, I posted an example of how one might use macros to create a tool for iterating through a file, line by line. Someone posted it to paste.lisp.org, to be viewed and commented on in #lisp. So, there’s been a couple of comments that I thought I might go through here. I’m just a novice at this, so this is a learning experience for me.
Refreshing your memory, here is what I had:
(defmacro dofile ((var filename) &body body)
`(with-open-file (stream filename)
(do ((,var (read-line stream nil nil) (read-line stream nil nil)))
((null ,var))
,@body)))
Missing a comma before “filename” in WITH-OPEN-FILE:
Not sure how I missed this, but right you are:
(defmacro dofile ((var filename) &body body)
`(with-open-file (stream ,filename)
(do ((,var (read-line stream nil nil) (read-line stream nil nil)))
((null ,var))
,@body)))
You don’t need the second NIL argument to READ-LINE:
This is straightforward enough. As described on the Common Lisp HyperSpec, the second argument defaults to nil, so we could have written the slightly more succint
(defmacro dofile ((var filename) &body body)
`(with-open-file (stream ,filename)
(do ((,var (read-line stream nil) (read-line stream nil)))
((null ,var))
,@body)))
Use the LOOP macro to improve readability:
I have to admit that I’m a novice at using the LOOP macro, because it seems very non-Lispy to me. Part of the reason I am motivated to use Lisp is because of the elegance and the LOOP macro just doesn’t really have it. I’m sure this is regularly debated in the Lisp community (For example, see here and here). In any case, here is a suggested way to rewrite the code:
(defmacro dofile ((var filename) &body body)
`(with-open-file (stream ,filename)
(loop for ,var = (read-line stream nil)
while ,var do
,@body)))
We’ve traded away one of the READ-LINE calls and the condition call for some LOOP syntax. This is slightly more succint but it makes me feel a little dirty. I read somewhere that there exists a macro that lets you LOOP in a way that feels more like LISP syntax; I will have to check this out (Does anybody have a link or experience to offer with this?).
Use GENSYM to prevent symbol conflicts with “stream”:
The idea here is that the current implementation prevents anyone from using the symbol “stream” in their body, which is a Bad Thing. So we use GENSYM to create a symbol that is guaranteed to be unique:
(defmacro dofile ((var filename) &body body)
(let ((stream (gensym)))
`(with-open-file (,stream ,filename)
(loop for ,var = (read-line ,stream nil)
while ,var do
,@body))))
So this is slightly more verbose, but much better form.
Thanks to all the people that commented on the original code!
UPDATE: I think the alternative to LOOP that I was thinking of was iterate.