From: Constantine Vetoshev
Subject: Simple macro question
Date: 
Message-ID: <m2isw3k4zl.fsf@news.dartmouth.edu>
Folks,

For the first time, I'm seriously starting to use macros in my CL
code. I wrote the following to facilitate reading lines of text from a
network socket and then doing something with them:

(defmacro with-line-from-socket ((line socket) &body body)
  `(mp:with-timeout (+timeout+ (return))
     (let ((,line (read-line ,socket nil)))
       (unless ,line (return))
       ,@body)))

The user is supposed to pass in an open socket and bind a variable,
then do something with the variable in the body:

(with-line-from-socket (something-read an-open-socket)
  (do-something-using something-read))

I know this is very incomplete with respect to error handling and all
the return calls. I only want to know if I managed to avoid the usual
set of bugs with macros. I've stared at it for a while, and I don't
see any variable capture or potential multiple evaluation
issues. Correct?

-- 
Thanks,
Constantine Vetoshev
(reverse (concatenate 'string "u" "d" "e" "."
                              "h" "t" "u" "o" "m" "t" "r" "a" "d" ·@"
                              "v" "e" "h" "s" "o" "t" "e" "v" "."
                              "e" "n" "i" "t" "n" "a" "t" "s" "n" "o" "c"))

From: Kent M Pitman
Subject: Re: Simple macro question
Date: 
Message-ID: <sfwk7gjo3nl.fsf@shell01.TheWorld.com>
Constantine Vetoshev <················@for.my.e-mail.address> writes:

> For the first time, I'm seriously starting to use macros in my CL
> code. I wrote the following to facilitate reading lines of text from a
> network socket and then doing something with them:
> 
> (defmacro with-line-from-socket ((line socket) &body body)
>   `(mp:with-timeout (+timeout+ (return))
>      (let ((,line (read-line ,socket nil)))
>        (unless ,line (return))
>        ,@body)))
> 
> The user is supposed to pass in an open socket and bind a variable,
> then do something with the variable in the body:
> 
> (with-line-from-socket (something-read an-open-socket)
>   (do-something-using something-read))
> 
> I know this is very incomplete with respect to error handling and all
> the return calls. I only want to know if I managed to avoid the usual
> set of bugs with macros. I've stared at it for a while, and I don't
> see any variable capture or potential multiple evaluation
> issues. Correct?

It should be fine.  I sometimes do

 (check-type line (and symbol (not constantp)) "a variable name")

Socket doesn't need that check because (a) you only use it once and 
(b) there are no order of evaluation issues in this coe fragment.

I know you siad you didn't want comments on this part, but...

You really shouldn't use macros like this with free RETURN calls, 
though.  It just begs for someone not to enclose it in the (BLOCK NIL ...)
and that's where you're most likely to get snared.

One way to do this is actually to add your own (BLOCK NIL ...)
but I don't recommend that because it's confusing when such blocks
appear out of nowhere.

I recommend doing one of the following kinds of approaches:

(defmacro with-line-from-socket ((line socket) &body body)
  `(block .socket.
     (flet ((socket-done () (return-from .socket.)))
       (mp:with-timeout (+timeout+ (socket-done))
         (let ((,line (read-line ,socket nil)))
           (unless ,line (socket-done))
           ,@body)))))

or, if you want to advertise the facility dynamically, you might
want the following, which by the way gives you some interactive help
in interactive error handling situations:

(defun socket-done ()
  (or (find-restart 'socket-done)
      (error "You are not reading from a socket.")))

(defmacro with-line-from-socket ((line socket) &body body)
  `(with-simple-restart (socket-done "Forceably stop reading from socket.")
     (mp:with-timeout (+timeout+ (socket-done))
       (let ((,line (read-line ,socket nil)))
         (unless ,line (socket-done))
         ,@body))))

Uh, test them? No I didn't.  Left as an exercise to the reader. ;)
Do let me know if you have any questions though.

In particular, ask me if it's not obvious why CATCH/THROW should never
be used as a replacement for WITH-SIMPLE-RESTART and FIND-RESTART, or
if it's confusing that restarts are being used even in the case where
there's no error handling going on.
From: Kaz Kylheku
Subject: Re: Simple macro question
Date: 
Message-ID: <cf333042.0302020615.22e79b27@posting.google.com>
Constantine Vetoshev <················@for.my.e-mail.address> wrote in message news:<··············@news.dartmouth.edu>...
> Folks,
> 
> For the first time, I'm seriously starting to use macros in my CL
> code. I wrote the following to facilitate reading lines of text from a
> network socket and then doing something with them:

With regard to your multiple evaluation and evaluation orders, the
following macro is fine because those arguments which are evaluated
are inserted into the expansion but once, and in the same order as
they appear in the original form.

> (defmacro with-line-from-socket ((line socket) &body body)
>   `(mp:with-timeout (+timeout+ (return))
>      (let ((,line (read-line ,socket nil)))
>        (unless ,line (return))
>        ,@body)))
> 
> The user is supposed to pass in an open socket and bind a variable,
> then do something with the variable in the body:

However, I have this design comment. The whole purpose of this macro
is to save you from repeatedly writing the above code: you want to
read a line from the socket, but with certain embellishments. This can
be done by a function rather than a macro. As a rule, when you want to
factor out some repetition, try to use a function first. A macro is
required only when you need to do something that requires custom
evaluation rules that can't be satisfied by a function.

For the present case, this will do just fine:

  (defun read-line-from-socket (line socket)
    (mp:with-timeout (+timeout+ (return))
      (read-line socket nil)))

If you still want inline expansion of this, write

  (declaim (inline read-line-from-socket))

before the function.

There is no need for a WITH-* binding construct over a simple
string---it's not a complex resource that requires lifetime management
like, say, an open file.
From: Kalle Olavi Niemitalo
Subject: Re: Simple macro question
Date: 
Message-ID: <87ptqbfej8.fsf@Astalo.y2000.kon.iki.fi>
Constantine Vetoshev <················@for.my.e-mail.address> writes:

> (with-line-from-socket (something-read an-open-socket)
>   (do-something-using something-read))

Consider:

  (with-line-from-socket (*something-read* an-open-socket)
    (declare (special *something-read*))
    (do-something))

If you want that to work, the macro has to separate the
declaration from the body and insert it outside the UNLESS form.
Unfortunately, this will make it somewhat more complex.
From: Constantine Vetoshev
Subject: Re: Simple macro question
Date: 
Message-ID: <m2ptqampuu.fsf@news.dartmouth.edu>
Constantine Vetoshev <················@for.my.e-mail.address> writes:

> I only want to know if I managed to avoid the usual
> set of bugs with macros. I've stared at it for a while, and I don't
> see any variable capture or potential multiple evaluation
> issues. Correct?

To everyone who responded: thank you for the prompt and thoughtful
replies.

-- 
Regards,
Constantine Vetoshev
(reverse (concatenate 'string "u" "d" "e" "."
                              "h" "t" "u" "o" "m" "t" "r" "a" "d" ·@"
                              "v" "e" "h" "s" "o" "t" "e" "v" "."
                              "e" "n" "i" "t" "n" "a" "t" "s" "n" "o" "c"))