From: Ramza Brown
Subject: A little help, with macros
Date: 
Message-ID: <RYmdnZ2dnZ3o7VafnZ2dnX3TYN-dnZ2dRVn-y52dnZ0@comcast.com>
I am trying to wrap my hands around macros, a little lost.
In this code, I want to have a macro for reading a file and the body 
would include the loop function.  The macro doesn't work, what would I 
need to change:

defmacro open-skel (filename &body body)
   `((format t "# .Opening file ~%")
   (let ((in-file
          (open file-name :if-does-not-exist nil)))
     (when in-file
       ,@body
       (close in-file)))
   (format t "# end ~%")))

(defun walk-open-times (fname)
   " The function can be used to open a file and parse
only the header. "
   (macroexpand-1 (open-skel fname
		   (loop for line = (read-line in-file nil)
			 while line do (format t "~a~%" line))))
   )

(defun walk-open-file (file-name)
   (format t "# .Opening file ~%")
   (let ((in-file
          (open file-name :if-does-not-exist nil)))
     (when in-file
       (loop for line = (read-line in-file nil)
             while line do (format t "~a~%" line))
       (close in-file)))
   (format t "# end ~%"))


-- 
Ramza from Atlanta
http://www.newspiritcompany.com

From: Peter Seibel
Subject: Re: A little help, with macros
Date: 
Message-ID: <m2mznmlhgm.fsf@gigamonkeys.com>
Ramza Brown <············@gmail.com> writes:

> I am trying to wrap my hands around macros, a little lost.
> In this code, I want to have a macro for reading a file and the body
> would include the loop function.  The macro doesn't work, what would I
> need to change:
>
> defmacro open-skel (filename &body body)
>    `((format t "# .Opening file ~%")
>    (let ((in-file
>           (open file-name :if-does-not-exist nil)))
>      (when in-file
>        ,@body
>        (close in-file)))
>    (format t "# end ~%")))

So your first problem (assuming the missing open paren before defmacro
is just a cut-n-paste error) is that a macro has to expand into a
single form. When you want to write a macro that expands into code
that does several things you can use a PROGN as the single form like
this:

  (defmacro open-skel (filename &body body)
    `(progn
       (format t "# .Opening file ~%")
       (let ((in-file
              (open file-name :if-does-not-exist nil)))
         (when in-file
           ,@body
           (close in-file)))
       (format t "# end ~%")))

However, this macro isn't really necessary (unless you really want
those FORMAT calls) since there's already a standard macro that does
what you want:

  (with-open-file (in filename :if-does-not-exist nil)
    ;; stuff
    )

WITH-OPEN-FILE is also better than open-skel in that the user passes
in the name of the stream to use where open-skel always uses a
variable named IN-FILE which is poor form. Also WITH-OPEN-FILE handles
closing the file with UNWIND-PROTECT so if the body signals an error
or THROWs out the file will still be closed.

>
> (defun walk-open-times (fname)
>    " The function can be used to open a file and parse
> only the header. "
>    (macroexpand-1 (open-skel fname
> 		   (loop for line = (read-line in-file nil)
> 			 while line do (format t "~a~%" line))))
>    )
>

Why are you calling MACROEXPAND-1 here? It's not, I suspect, doing
what you think it's doing.


> (defun walk-open-file (file-name)
>    (format t "# .Opening file ~%")
>    (let ((in-file
>           (open file-name :if-does-not-exist nil)))
>      (when in-file
>        (loop for line = (read-line in-file nil)
>              while line do (format t "~a~%" line))
>        (close in-file)))
>    (format t "# end ~%"))

So this can be written:

  (defun walk-open-file (file-name)
    (format t "# .Opening file~%")
    (with-open-file (in-file file-name :if-does-not-exist nil)
      (when in-file
        (loop for line = (read-line in-file nil)
            while line do (format t "~a~%" line))))
    (format t "# end~%"))

-Peter

-- 
Peter Seibel           * ·····@gigamonkeys.com
Gigamonkeys Consulting * http://www.gigamonkeys.com/
Practical Common Lisp  * http://www.gigamonkeys.com/book/
From: Ramza Brown
Subject: Re: A little help, with macros
Date: 
Message-ID: <EZSdnZ2dnZ3EcQ6gnZ2dnSPKYN-dnZ2dRVn-0Z2dnZ0@comcast.com>
Peter Seibel wrote:
> Ramza Brown <············@gmail.com> writes:
> 
> 
>>I am trying to wrap my hands around macros, a little lost.
>>In this code, I want to have a macro for reading a file and the body
>>would include the loop function.  The macro doesn't work, what would I
>>need to change:
>>
>>defmacro open-skel (filename &body body)
>>   `((format t "# .Opening file ~%")
>>   (let ((in-file
>>          (open file-name :if-does-not-exist nil)))
>>     (when in-file
>>       ,@body
>>       (close in-file)))
>>   (format t "# end ~%")))
> 
> 
> So your first problem (assuming the missing open paren before defmacro
> is just a cut-n-paste error) is that a macro has to expand into a
> single form. When you want to write a macro that expands into code
> that does several things you can use a PROGN as the single form like
> this:
> 
>   (defmacro open-skel (filename &body body)
>     `(progn
>        (format t "# .Opening file ~%")
>        (let ((in-file
>               (open file-name :if-does-not-exist nil)))
>          (when in-file
>            ,@body
>            (close in-file)))
>        (format t "# end ~%")))
> 
> However, this macro isn't really necessary (unless you really want
> those FORMAT calls) since there's already a standard macro that does
> what you want:
> 
>   (with-open-file (in filename :if-does-not-exist nil)
>     ;; stuff
>     )
> 
> WITH-OPEN-FILE is also better than open-skel in that the user passes
> in the name of the stream to use where open-skel always uses a
> variable named IN-FILE which is poor form. Also WITH-OPEN-FILE handles
> closing the file with UNWIND-PROTECT so if the body signals an error
> or THROWs out the file will still be closed.
> 
> 
>>(defun walk-open-times (fname)
>>   " The function can be used to open a file and parse
>>only the header. "
>>   (macroexpand-1 (open-skel fname
>>		   (loop for line = (read-line in-file nil)
>>			 while line do (format t "~a~%" line))))
>>   )
>>
> 
> 
> Why are you calling MACROEXPAND-1 here? It's not, I suspect, doing
> what you think it's doing.
> 
> 
> 
>>(defun walk-open-file (file-name)
>>   (format t "# .Opening file ~%")
>>   (let ((in-file
>>          (open file-name :if-does-not-exist nil)))
>>     (when in-file
>>       (loop for line = (read-line in-file nil)
>>             while line do (format t "~a~%" line))
>>       (close in-file)))
>>   (format t "# end ~%"))
> 
> 
> So this can be written:
> 
>   (defun walk-open-file (file-name)
>     (format t "# .Opening file~%")
>     (with-open-file (in-file file-name :if-does-not-exist nil)
>       (when in-file
>         (loop for line = (read-line in-file nil)
>             while line do (format t "~a~%" line))))
>     (format t "# end~%"))
> 
> -Peter
> 

That is ironic, part of that code is yours, the file open stuff.

-- 
Ramza from Atlanta
http://www.newspiritcompany.com
From: Peter Seibel
Subject: Re: A little help, with macros
Date: 
Message-ID: <m2iryald83.fsf@gigamonkeys.com>
Ramza Brown <············@gmail.com> writes:

> That is ironic, part of that code is yours, the file open stuff.

Hmmm. Keep reading--at least through the end of the section "Closing
Files."

-Peter

-- 
Peter Seibel           * ·····@gigamonkeys.com
Gigamonkeys Consulting * http://www.gigamonkeys.com/
Practical Common Lisp  * http://www.gigamonkeys.com/book/
From: Thomas A. Russ
Subject: Re: A little help, with macros
Date: 
Message-ID: <ymid5off3ab.fsf@sevak.isi.edu>
Ramza Brown <············@gmail.com> writes:

> 
> I am trying to wrap my hands around macros, a little lost.
> In this code, I want to have a macro for reading a file and the body 
> would include the loop function.  The macro doesn't work, what would I 
> need to change:

Well, for starters, you could look at what sort of error message you
get.  What does your Lisp system complain about?

> 
> (defmacro open-skel (filename &body body)
>    `((format t "# .Opening file ~%")
>      (let ((in-file
>               (open file-name :if-does-not-exist nil)))
>         (when in-file
>             ,@body
>             (close in-file)))
>       (format t "# end ~%")))

First of all, you need to have the macro return valid lisp
code.  If you want multiple forms to execute, you can't have the macro
just return a list of them.  This is the place where using MACROEXPAND-1
is useful.  That function is essentially a debugging tool that will show
you what your macro returns.  In your case calling it with a simple test
case like 

  (macroexpand-1 '(open-skel "myfile" (print in-file)))

gives you the following code:

  ((FORMAT T "# .Opening file ~%")
   (LET ((IN-FILE (OPEN FILE-NAME :IF-DOES-NOT-EXIST NIL)))
     (WHEN IN-FILE (PRINT IN-FILE) (CLOSE IN-FILE)))
   (FORMAT T "# end ~%"))

But that is not valid Lisp code.  If you want to return multiple forms
from a macro, you want them to be in a PROGN form.  (It sems that
perhaps the FORMAT statements are to help you debug the macro, but you
would still need to either wrap everything in a PROGN or else put them
inside the LET form.

OK, now if you look at the expansion, you will also see that the
filename argument "myfile" has completely disappeared.  That is because
it was never substituted into the macro body at any place.  You
substitute the BODY, but not the FILENAME argument.

It is also generally a Bad Idea(tm) to bind a specifically named
variable for use in macro code.  Aside from requiring any user of your
macro to know the identity of the variable, it also makes it impossible
for you to nest invocations of your macro.  If someone wanted to write

   (open-skel "file1"
    (open-skel "file2"
       ...
       ;; Alternately write lines from "file1" and "file2"
      ???
       ...))

There is no way to reference the open stream for "file1" because the
inner macro shadows the binding.

One final issue is that there is also a potential problem with error
handling.  Since you are just starting out, you may not have gotten to
the point where this concerns you, but if something goes wrong in the
BODY of the macro, the file that is opened will not be closed.  The
general solution to this is to use UNWIND-PROTECT, but since the case of
file access is so frequently encountered, Common Lisp already provides
a built-in macro that take care of that issue for you: WITH-OPEN-FILE.

I'll assume that you will be happy to use the built-in construct and not
try to write your own, although it's actually not the difficult and
could be a useful learning exercise.


> (defun walk-open-times (fname)
>    " The function can be used to open a file and parse
> only the header. "
>    (macroexpand-1 (open-skel fname
> 		   (loop for line = (read-line in-file nil)
> 			 while line do (format t "~a~%" line))))
>    )

You should not be calling MACROEXPAND-1 here.  It is sufficient to just
use the macro as a normal lisp form.  That is one of the stengths of
macros.  They let you expand the language without making it seem like an
expansion.  This function should look like:

(defun walk-open-times (fname)
    " The function can be used to open a file and parse
 only the header. "
   (open-skel fname
       (loop for line = (read-line in-file nil)
	     while line do (format t "~a~%" line))))

But actually, I suspect from your initial description that this is
perhaps the actual macro that you want to write.  I'll revisit it below.

> (defun walk-open-file (file-name)
>    (format t "# .Opening file ~%")
>    (let ((in-file
>           (open file-name :if-does-not-exist nil)))
>      (when in-file
>        (loop for line = (read-line in-file nil)
>              while line do (format t "~a~%" line))
>        (close in-file)))
>    (format t "# end ~%"))

Normally, what I like to do is to start with a concrete example of the
final code I would like to produce (such as the function above) and then
extract the parts I want to have into a macro.  I then pull the variable
parts out and make them macro arguments, taking care to get the
substitution correct.

So, let me rewrite this slightly to use WITH-OPEN-FILE and then we'll
proceed with turning it into a macro:

(defun walk-open-file (file-name)
    (format t "# .Opening file ~%")
    (with-open-file (in-file file-name
                             :direction :input
                             :if-does-not-exist nil)
        (when in-file 
           (loop for line = (read-line in-file nil)
                 while line
                 do (format t "~a~%" line))))
    (format t "# end ~%"))

I'll assume you'll want a macro that will iterate over just the lines in
a file.  Let's name it DO-FILE-LINES, and what we want is to have it
produce the following code:

   (with-open-file (in-file file-name
                             :direction :input
                             :if-does-not-exist nil)
        (when in-file 
           (loop for line = (read-line in-file nil)
                 while line
                 do (format t "~a~%" line))))

with the iterator variable LINE and FILE-NAME supplied as parameters.
We want the iterator variable as a parameters to allow nesting of these
expressions.  So our first take on the macro would be the following:

(defmacro do-file-lines (variable file-name &body body)
  `(with-open-file (in-file ,file-name
                             :direction :input
                             :if-does-not-exist nil)
        (when in-file 
           (loop for ,variable = (read-line in-file nil)
                 while ,variable
                 do ,@body))))

A quick test shows that this gives us what we wanted:

 (macroexpand-1 
    '(do-file-lines line "myfile" (format t "~a~%" line)))

(WITH-OPEN-FILE (IN-FILE "myfile" :DIRECTION :INPUT :IF-DOES-NOT-EXIST NIL)
  (WHEN IN-FILE (LOOP FOR LINE = (READ-LINE IN-FILE NIL)
                      WHILE LINE
                      DO (FORMAT T "~a~%" LINE))))

This looks fine for now, but even though we managed to specify the
iteration variable (so the user doesn't have to know its name, and also
to allow nesting of these forms), we still haven't solved the problem of
the binding of the filename IN-FILE.  Although it is now bound in a
macro invocation, it is effectively the same as having the LET.  So we
need to find a way of avoiding the so-called "Variable Capture Problem."

The standard way of doing that in Lisp is to introduce newly created
symbols which will therefore not conflict with anything.  Since the use
of the symbol is limited to the code produced by the macro expansion,
there is no need for the user to know anything about it.  Here we take
advantage of the fact that you get to have some Lisp code execute during
the macro expansion (the part outside the backquote) and then be used in
the actual expansion.  This makes use of GENSYM to create a new
(non-interned) symbol:

(defmacro do-file-lines (loop-variable file-name &body body)
  (let ((file-variable (gensym "FILE-")))
    `(with-open-file (,file-variable ,file-name
                                     :direction :input
                                     :if-does-not-exist nil)
         (when ,file-variable
            (loop for ,loop-variable = (read-line ,file-variable nil)
                  while ,loop-variable
                  do ,@body)))))

So, now when we expand the code from our new macro we get

  (macroexpand-1 
      '(do-file-lines line "myfile"
           (format t "~a~%" line)))

  (WITH-OPEN-FILE (#:FILE-110 "myfile" :DIRECTION :INPUT :IF-DOES-NOT-EXIST NIL)
    (WHEN #:FILE-110
      (LOOP FOR LINE = (READ-LINE #:FILE-110 NIL)
            WHILE LINE
            DO (FORMAT T "~a~%" LINE))))

which gives us a safe, nestable macro that iterates over all the lines
in an assigned file.  Now if we want to get a bit fancier, we can
further take advantage of the fact that macro (but not function)
argument lists can also describe more of a structure.  That is what is
used, for example, in the WITH-OPEN-FILE macro.  That allows us to group
the iteration variable and file name together.  That macro would look
like this:

(defmacro do-file-lines ((loop-variable file-name) &body body)
  (let ((file-variable (gensym "FILE-")))
    `(with-open-file (,file-variable ,file-name
                                     :direction :input
                                     :if-does-not-exist nil)
         (when ,file-variable
            (loop for ,loop-variable = (read-line ,file-variable nil)
                  while ,loop-variable
                  do ,@body)))))

and it would be invoked like this
      (do-file-lines (line "myfile")
          (format t "~a~%" line)))
with parentheses around the first two arguments.

OK, now putting this all together, your original function could then use
this macro to be coded like:

(defun walk-open-file (file-name)
    (format t "# .Opening file ~%")
    (do-file-lines (line file-name)
       (format t "~a~%" line))
    (format t "# end ~%"))

You an even use this in a nested form, if it made sense.

For a simple example, consider the following files:
FILE-1:
Do you like lisp?
Is Lisp the best language in the world?
Are you having fun yet?

FILE-2:
Yes
No
Maybe
I don't know

(defun build-multiple-choice-survey (question-file answer-file)
  (do-file-lines (q question-file)
    (format t "~a~%" q)
    (do-file-lines (a answer-file)
       (format t "   ~a~%" a))
    (format t "~%")))


-- 
Thomas A. Russ,  USC/Information Sciences Institute