From: Stefan Nobis
Subject: Macrology and Closures
Date: 
Message-ID: <87psx9go0d.fsf@snobis.de>
Hi.

I have a macro which generate a closure and puts that closure in a
hash-table. I also want to use a macro in that closure, but it
don't work. This is my poor try:

(defmacro cmdexit (text &key (error-p nil) (quit-p nil))
  `(return-from gtpcmd-block `(,text ,error-p ,quit-p))))

(defmacro def-command (name args &body body)
  (let ((fun (gensym)))
  `(let ((,fun (lambda ,args
                 (block gtpcmd-block ,@body))))
    (setf (gethash ,name *commands*) ,fun))))

(def-command "foo" (a b)
  (when a (cmdexit "Error" :error-p t))
  (cmdexit "foo"))

The problem is, cmdexit does not get expanded this way. And i get
warnings, that a and b are not used. I tried this with cmdexit
being a function but than return-form doesn't work.

Any ideas how this may work including the block and return-from
(or something else with the same result)?

-- 
Stefan.

From: Sam Steingold
Subject: Re: Macrology and Closures
Date: 
Message-ID: <u64z1yvy7.fsf@gnu.org>
> * Stefan Nobis <······@tzk.qr> [2005-04-05 18:57:22 +0200]:
>
> (defmacro cmdexit (text &key (error-p nil) (quit-p nil))
>   `(return-from gtpcmd-block `(,text ,error-p ,quit-p))))
                               ^
                               this should be a simple quote

> And i get warnings, that a and b are not used.

b is indeed not used.

> (defmacro def-command (name args &body body)
>   (let ((fun (gensym)))
>   `(let ((,fun (lambda ,args
>                  (block gtpcmd-block ,@body))))
>     (setf (gethash ,name *commands*) ,fun))))

this is too complex.

(defparameter *commands* (make-hash-table :test 'equal))

(defmacro cmdexit (text &key (error-p nil) (quit-p nil))
  `(return-from gtpcmd-block '(,text ,error-p ,quit-p)))

(defmacro def-command (name args &body body)
  `(setf (gethash ,name *commands*)
         (lambda ,args (block gtpcmd-block ,@body))))

(def-command "foo" (a b)
  (when a (cmdexit "Error" :error-p t))
  (cmdexit "foo"))

(funcall (gethash "foo" *commands*) 1 2)
==> ("Error" T NIL)

(funcall (gethash "foo" *commands*) nil "bar")
==> ("foo" NIL NIL)
-- 
Sam Steingold (http://www.podval.org/~sds) running w2k
<http://www.camera.org> <http://www.honestreporting.com>
<http://pmw.org.il/> <http://www.memri.org/>
WHO ATE MY BREAKFAST PANTS?
From: Stefan Nobis
Subject: Re: Macrology and Closures
Date: 
Message-ID: <87ll7xgj6w.fsf@snobis.de>
Sam Steingold <···@gnu.org> writes:

>> (defmacro def-command (name args &body body)
>>   (let ((fun (gensym)))
>>   `(let ((,fun (lambda ,args
>>                  (block gtpcmd-block ,@body))))
>>     (setf (gethash ,name *commands*) ,fun))))

> this is too complex.

I see. Thank you.

> (defmacro cmdexit (text &key (error-p nil) (quit-p nil))
>   `(return-from gtpcmd-block '(,text ,error-p ,quit-p)))

> (def-command "foo" (a b)
>   (when a (cmdexit "Error" :error-p t))
>   (cmdexit "foo"))

Now i want to do this:

  (def-command "foo" (a b)
    (when a (cmdexit "Error" :error-p t))
    (cmdexit (if b "foo" "bar")))

cmdexit is a macro, so the arguments are not evaluated and this
doesn't work (with the above definition of cmdexit). Is there a
way to define cmdexit to work with arbitrary complex forms (in
parameter text)?

Complex forms as parameters to cmdexit work if cmdexit is a
function. So is there a way to define a function cmdexit with the
ability to achieve the same result as cmdexit (namely exit from
the block inside the closure)?

-- 
Stefan.
From: Sam Steingold
Subject: Re: Macrology and Closures
Date: 
Message-ID: <ur7hpxb87.fsf@gnu.org>
> * Stefan Nobis <······@tzk.qr> [2005-04-05 20:41:27 +0200]:
>
> Now i want to do this:
>
>   (def-command "foo" (a b)
>     (when a (cmdexit "Error" :error-p t))
>     (cmdexit (if b "foo" "bar")))
>
> cmdexit is a macro, so the arguments are not evaluated and this
> doesn't work (with the above definition of cmdexit). Is there a
> way to define cmdexit to work with arbitrary complex forms (in
> parameter text)?

you have to modify cmdexit not to quote text:

(defmacro cmdexit (text &key (error-p nil) (quit-p nil))
  `(return-from gtpcmd-block (list ,text ',error-p ',quit-p)))

(def-command "foo" (a b)
    (when a (cmdexit "Error" :error-p t))
    (cmdexit (if b "foo" "bar")))

(funcall (gethash "foo" *commands*) nil nil)
  ==> ("foo" NIL NIL)

> Complex forms as parameters to cmdexit work if cmdexit is a
> function. So is there a way to define a function cmdexit with the
> ability to achieve the same result as cmdexit (namely exit from the
> block inside the closure)?

use THROW & CATCH.

-- 
Sam Steingold (http://www.podval.org/~sds) running w2k
<http://pmw.org.il/> <http://www.openvotingconsortium.org/>
<http://www.honestreporting.com> <http://www.camera.org>
I want Tamagochi! -- What for?  Your pet hamster is still alive!
From: Stefan Nobis
Subject: Re: Macrology and Closures
Date: 
Message-ID: <87hdilgeju.fsf@snobis.de>
Sam Steingold <···@gnu.org> writes:

>> cmdexit is a macro, so the arguments are not evaluated and this
>> doesn't work (with the above definition of cmdexit). Is there a
>> way to define cmdexit to work with arbitrary complex forms (in
>> parameter text)?

> you have to modify cmdexit not to quote text:

Argh! It could be so easy if i'm not that blind. Thank you very
much! Now it works.

-- 
Stefan.
From: Kalle Olavi Niemitalo
Subject: Re: Macrology and Closures
Date: 
Message-ID: <87y8bxf0jm.fsf@Astalo.kon.iki.fi>
Stefan Nobis <······@gmx.de> writes:

> cmdexit is a macro, so the arguments are not evaluated and this
> doesn't work (with the above definition of cmdexit). Is there a
> way to define cmdexit to work with arbitrary complex forms (in
> parameter text)?

If the macro should recognize keyword arguments at macroexpand time:

  (defmacro cmdexit (text &key error-p quit-p)
    ;; Warning: This may evaluate arguments in the wrong order.
    `(return-from gtpcmd-block `(,,text ,,error-p ,,quit-p)))

If the expansion should recognize keyword arguments at run time:

  (defmacro cmdexit (&rest args)
    `(destructuring-bind (text &key error-p quit-p) `(,,@args)
       (return-from gtpcmd-block `(,text ,error-p ,quit-p))))

or with a lambda form:

  (defmacro cmdexit (&rest args)
    `((lambda (text &key error-p quit-p)
        (return-from gtpcmd-block `(,text ,error-p ,quit-p)))
      ,@args))

> Complex forms as parameters to cmdexit work if cmdexit is a
> function. So is there a way to define a function cmdexit with the
> ability to achieve the same result as cmdexit (namely exit from
> the block inside the closure)?

Yes, by defining the function inside the block:

  (defmacro def-command (name args &body body)
    `(setf (gethash ,name *commands*)
           (lambda ,args
             (block gtpcmd-block
               (flet ((cmdexit (text &key error-p quit-p)
                        (return-from gtpcmd-block `(,text ,error-p ,quit-p))))
                 ,@body)))))

Add gensyms according to taste.
From: Stefan Nobis
Subject: Re: Macrology and Closures
Date: 
Message-ID: <87ekdomjg8.fsf@snobis.de>
Kalle Olavi Niemitalo <···@iki.fi> writes:

> If the macro should recognize keyword arguments at macroexpand time:

>   (defmacro cmdexit (text &key error-p quit-p)
>     ;; Warning: This may evaluate arguments in the wrong order.
>     `(return-from gtpcmd-block `(,,text ,,error-p ,,quit-p)))

Hmmm... why may arguments be evaluated in the wrong order? And is
there a difference to (list ,text ,error-p ,quit-p) [i assume:
no]?

> If the expansion should recognize keyword arguments at run time:

>   (defmacro cmdexit (&rest args)
>     `(destructuring-bind (text &key error-p quit-p) `(,,@args)
>        (return-from gtpcmd-block `(,text ,error-p ,quit-p))))

What are the pros and cons of this two solutions? I don't quite
understand the difference.

> or with a lambda form:

>   (defmacro cmdexit (&rest args)
>     `((lambda (text &key error-p quit-p)
>         (return-from gtpcmd-block `(,text ,error-p ,quit-p)))
>       ,@args))

Why ,@args at the end when return-from already exits the block? As
i understand it, the line ,@args is never reached. Am i wrong?

> Yes, by defining the function inside the block:

>   (defmacro def-command (name args &body body)
>     `(setf (gethash ,name *commands*)
>            (lambda ,args
>              (block gtpcmd-block
>                (flet ((cmdexit (text &key error-p quit-p)
>                         (return-from gtpcmd-block `(,text ,error-p ,quit-p))))
>                  ,@body)))))

This way i get the same local function many times. Isn't this a
bit bloating?

-- 
Stefan.
From: ivant
Subject: Re: Macrology and Closures
Date: 
Message-ID: <1112800697.783399.194690@l41g2000cwc.googlegroups.com>
Stefan Nobis wrote:
> Kalle Olavi Niemitalo <···@iki.fi> writes:
> > or with a lambda form:
>
> >   (defmacro cmdexit (&rest args)
> >     `((lambda (text &key error-p quit-p)
> >         (return-from gtpcmd-block `(,text ,error-p ,quit-p)))
> >       ,@args))
>
> Why ,@args at the end when return-from already exits the block? As
> i understand it, the line ,@args is never reached. Am i wrong?

Macroexpand and macroexpand-1 are your friends:

(cmdexit "foo") expands to
((LAMBDA (TEXT &KEY ERROR-P QUIT-P)
   (RETURN-FROM GTPCMD-BLOCK `(,TEXT ,ERROR-P ,QUIT-P)))
 "foo")

This is a bit Schemish sintax, though it's perfectly legal in Common
Lisp too.  It's roughly equivalent to:
(APPLY #'(LAMBDA (TEXT &KEY ERROR-P QUIT-P)
           (RETURN-FROM GTPCMD-BLOCK `(,TEXT ,ERROR-P ,QUIT-P)))
       '("foo"))

As you can see, the lambda gets applied to the arguments, and when it's
applied it returns from the block.  But the arguments get evaluated
before that.

HTH,
Ivan
From: Barry Margolin
Subject: Re: Macrology and Closures
Date: 
Message-ID: <barmar-1CE0C5.08332906042005@comcast.dca.giganews.com>
In article <··············@snobis.de>, Stefan Nobis <······@gmx.de> 
wrote:

> Kalle Olavi Niemitalo <···@iki.fi> writes:
> 
> > If the macro should recognize keyword arguments at macroexpand time:
> 
> >   (defmacro cmdexit (text &key error-p quit-p)
> >     ;; Warning: This may evaluate arguments in the wrong order.
> >     `(return-from gtpcmd-block `(,,text ,,error-p ,,quit-p)))
> 
> Hmmm... why may arguments be evaluated in the wrong order? And is
> there a difference to (list ,text ,error-p ,quit-p) [i assume:
> no]?

I don't think it will evaluate them in the wrong order.  And your 
assumption is correct.

> 
> > If the expansion should recognize keyword arguments at run time:
> 
> >   (defmacro cmdexit (&rest args)
> >     `(destructuring-bind (text &key error-p quit-p) `(,,@args)
> >        (return-from gtpcmd-block `(,text ,error-p ,quit-p))))
> 
> What are the pros and cons of this two solutions? I don't quite
> understand the difference.

Consider the following weird code:

(defun test-it (text error-or-quit)
  (let ((kwd (ecase error-or-quit
               (:error :error-p)
               (:quit :quit-p))))
    (cmdexit text kwd t)))

But if you want to support this kind of thing, you should probably make 
it a function and use CATCH/THROW rather than BLOCK/RETURN-FROM.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Zach Beane
Subject: Re: Macrology and Closures
Date: 
Message-ID: <m3ekdolyp4.fsf@unnamed.xach.com>
Barry Margolin <······@alum.mit.edu> writes:

>> >   (defmacro cmdexit (text &key error-p quit-p)
>> >     ;; Warning: This may evaluate arguments in the wrong order.
>> >     `(return-from gtpcmd-block `(,,text ,,error-p ,,quit-p)))
>> 
>> Hmmm... why may arguments be evaluated in the wrong order? And is
>> there a difference to (list ,text ,error-p ,quit-p) [i assume:
>> no]?
>
> I don't think it will evaluate them in the wrong order.  

What about in this case?

   (let ((bar t))
     (cmdexit "foo" :quit-p (setf bar nil) :error-p bar))

Zach
From: Barry Margolin
Subject: Re: Macrology and Closures
Date: 
Message-ID: <barmar-6160D4.16084606042005@comcast.dca.giganews.com>
In article <··············@unnamed.xach.com>,
 Zach Beane <····@xach.com> wrote:

> Barry Margolin <······@alum.mit.edu> writes:
> 
> >> >   (defmacro cmdexit (text &key error-p quit-p)
> >> >     ;; Warning: This may evaluate arguments in the wrong order.
> >> >     `(return-from gtpcmd-block `(,,text ,,error-p ,,quit-p)))
> >> 
> >> Hmmm... why may arguments be evaluated in the wrong order? And is
> >> there a difference to (list ,text ,error-p ,quit-p) [i assume:
> >> no]?
> >
> > I don't think it will evaluate them in the wrong order.  
> 
> What about in this case?
> 
>    (let ((bar t))
>      (cmdexit "foo" :quit-p (setf bar nil) :error-p bar))
> 
> Zach

You're right.  I'm not sure there's any way to solve this if the macro 
needs to access keyword arguments at expansion time.  The problem is 
that the keyword argument parser doesn't retain any information about 
the original order of arguments.

For some macros you can solve it by having it expand into an application 
of a function:

(defun make-cmdexit-result (text &key error-p quit-p)
  `(,text ,error-p ,quit-p)))

(defmacro cmdexit (text &rest options &key error-p quit-p)
  (declare (ignore error-p quit-p)) ;; just there for self-documentation
  `(return-from gtpcmd-block
     (make-cmdexit-result ,text ,@options)))

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Stefan Nobis
Subject: Re: Macrology and Closures
Date: 
Message-ID: <87is2zwow1.fsf@snobis.de>
Barry Margolin <······@alum.mit.edu> writes:

> In article <··············@unnamed.xach.com>,
>  Zach Beane <····@xach.com> wrote:

>>    (let ((bar t))
>>      (cmdexit "foo" :quit-p (setf bar nil) :error-p bar))

> You're right.

Please, can you explain this. Why are the arguments evaluated in
the wrong order?

-- 
Stefan.
From: Barry Margolin
Subject: Re: Macrology and Closures
Date: 
Message-ID: <barmar-B0BD2C.20470406042005@comcast.dca.giganews.com>
In article <··············@snobis.de>, Stefan Nobis <······@gmx.de> 
wrote:

> Barry Margolin <······@alum.mit.edu> writes:
> 
> > In article <··············@unnamed.xach.com>,
> >  Zach Beane <····@xach.com> wrote:
> 
> >>    (let ((bar t))
> >>      (cmdexit "foo" :quit-p (setf bar nil) :error-p bar))
> 
> > You're right.
> 
> Please, can you explain this. Why are the arguments evaluated in
> the wrong order?

The expansion of the macro will contain

(list text error-p quit-p)

Since arguments to a function are evaluated left-to-right, this will 
evaluate error-p before quit-p.  But in your original call to cmdexit, 
you expect the parameter to :quit-p to be evaluated before the parameter 
to :error-p.  And since the parameter to :quit-p has a side effect on 
the parameter to :error-p, it makes a difference.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Stefan Nobis
Subject: Re: Macrology and Closures
Date: 
Message-ID: <87mzscvuvm.fsf@snobis.de>
Barry Margolin <······@alum.mit.edu> writes:

> Consider the following weird code:

> (defun test-it (text error-or-quit)
>   (let ((kwd (ecase error-or-quit
>                (:error :error-p)
>                (:quit :quit-p))))
>     (cmdexit text kwd t)))

Wow, cool. This never comes to my mind. But now, as i think about
it, i think i see the trick (for the above to work, cmdexit has to
be a function, right). Common Lisp is really very flexible and
dynamic. Nice hint. Thank you very much.

> But if you want to support this kind of thing

No. The purpose of my two macros is only to ease writing a bunch
of small wrapper functions. They are very straightforward and i
don't need such things in them.

-- 
Stefan.