Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

generating new names in macro -- style question

4 views
Skip to first unread message

Albert Krewinkel

unread,
Dec 15, 2008, 8:11:35 PM12/15/08
to
I'm struggling with my sense of beauty here, having difficulties to
decide if the following is a good or bad idea. A few comments about the
following code with respect to good lisp style would be highly
appreciated.

I'm working with graphs (the mathematical structures) and wrote a couple
of functions which all take a graph as it's first argument. To safe
myself some keystrokes, I wrote a macro which takes a graph as argument,
and provides curried functions of the graph-functions, also changing
the name to avoid confusion (and to safe a few more keystrokes).

Therefore instead of

(progn
(graph-add-vertex some-graph 4)
(graph-delete-vertex some-graph 5))

now I can do

(with-graph (some-graph)
(add-vertex 4)
(delete-vertex 5)

This comes in very handy when I have to do multiple operations on the
same object. However, it feels strange to generate new symbols, so I
wonder if there is a better way doing this kind of thing. Global
variables would work, but that would break my attempt of doing things in
a functional manner.

Thanks
A


(defgeneric graph-add-vertex (graph ...) ...)

(defparameter *graph-functions*
'(graph-add-vertex graph-delete-vertex graph-vertex-outdegree))

(defun graph-flet-curry (graph function new-function)
`(,new-function (&rest args)
(apply #',function ,graph args)))


;; function `remove-symbol-prefix' alters a symbol by removing a prefix
;; (function definition not included for shortness)

(defmacro with-graph ((graph) &body body)
(flet ((graph-flet-curry (graph function new-function)
`(,new-function (&rest args)
(apply #',function ,graph args))))
`(flet ,(loop for function in *graph-functions*
collect (graph-flet-curry graph
function
(remove-symbol-prefix function
'graph)))
,@body)))

Madhu

unread,
Dec 15, 2008, 8:38:14 PM12/15/08
to

* Albert Krewinkel <m2prjs2...@gmx.net> :
Wrote on Mon, 15 Dec 2008 17:11:35 -0800:

| I'm working with graphs (the mathematical structures) and wrote a couple
| of functions which all take a graph as it's first argument. To safe
| myself some keystrokes, I wrote a macro which takes a graph as argument,
| and provides curried functions of the graph-functions, also changing
| the name to avoid confusion (and to safe a few more keystrokes).
|
| Therefore instead of
|
| (progn
| (graph-add-vertex some-graph 4)
| (graph-delete-vertex some-graph 5))
|

| (with-graph (some-graph)
| (add-vertex 4)
| (delete-vertex 5)
|
| This comes in very handy when I have to do multiple operations on the
| same object. However, it feels strange to generate new symbols, so I
| wonder if there is a better way doing this kind of thing. Global
| variables would work, but that would break my attempt of doing things in
| a functional manner.

This functional aesthetic does not apply to common lisp in many cases.
There are clear advantages in the CL approach:

(defvar *graph* nil "Default graph target for all operations if non-NULL.")

(defun add-vertex (vertex &optional (graph *graph*)) ...)
(defun delete-vertex (vertex &optional (graph *graph*)) ...)

All you have done is switch the argument order. You get back your
with-graph macro in this form:

(defmacro with-graph (graph &body body) `(let ((*graph* graph)) ,@body))

--
Madhu

Albert Krewinkel

unread,
Dec 15, 2008, 9:05:15 PM12/15/08
to
Madhu <eno...@meer.net> writes:

True, but it also renders &rest useless. Imagine, e.g. a hypergraph,
where an edge may connect 0 or more edges. Having
(defgeneric vertices-adjacent-p (vertices &optional graph) ...)
is pretty ugly,
(defgeneric vertices-adjacent-p (graph &rest vertices) ...)
is much nicer.

Also, method-dispatching on the graph requires an additional
helper-function `graph-vertices-adjacent-p', so we are right we started.

Anyway, thanks for the answer.

Madhu

unread,
Dec 15, 2008, 9:29:06 PM12/15/08
to

* Albert Krewinkel <m2ljug2...@gmx.net> :
Wrote on Mon, 15 Dec 2008 18:05:15 -0800:

| Madhu <eno...@meer.net> writes:
|> * Albert Krewinkel <m2prjs2...@gmx.net> :
|> Wrote on Mon, 15 Dec 2008 17:11:35 -0800:

|> | This comes in very handy when I have to do multiple operations on the
|> | same object. However, it feels strange to generate new symbols, so I
|> | wonder if there is a better way doing this kind of thing. Global
|> | variables would work, but that would break my attempt of doing things in
|> | a functional manner.
|>
|> This functional aesthetic does not apply to common lisp in many cases.
|> There are clear advantages in the CL approach:
|>
|> (defvar *graph* nil "Default graph target for all operations if non-NULL.")
|>
|> (defun add-vertex (vertex &optional (graph *graph*)) ...)
|> (defun delete-vertex (vertex &optional (graph *graph*)) ...)
|>
|> All you have done is switch the argument order. You get back your
|> with-graph macro in this form:
|>
|> (defmacro with-graph (graph &body body) `(let ((*graph* graph)) ,@body))
|
| True, but it also renders &rest useless. Imagine, e.g. a hypergraph,
| where an edge may connect 0 or more edges. Having
| (defgeneric vertices-adjacent-p (vertices &optional graph) ...)
| is pretty ugly,
| (defgeneric vertices-adjacent-p (graph &rest vertices) ...)
| is much nicer.

I've gone back and forth on this on many problems with this structure.
My current style is to write

VERTICES-ADJACENT-P (GRAPH VERTICES)

as the basic implementation function, and add the syntactically a
friendlier function as an ordinary function which calls this after
massaging parameters.


| Also, method-dispatching on the graph requires an additional
| helper-function `graph-vertices-adjacent-p', so we are right we
| started.

I suspect you would need a helper function to do dispatch anyway if
you wanted &REST. (This appears to be a different concern)

Anyway the basic point I wanted to make was it was NOT WRONG to use
specials in implementing something which lets you write your code in a
functional style. At some point all the code in "functional languages"
get implemented using "non-functional" devices. There is no point in
restricting the devices you can use when they are already available (and
can be used somewhat elegantly).

[NOTE In CL as soon as you wrote that macro, you are already the
language implementor for the rest of your program]

--
Madhu

Kaz Kylheku

unread,
Dec 16, 2008, 12:01:21 AM12/16/08
to
On 2008-12-16, Albert Krewinkel <krew...@gmx.net> wrote:
> I'm struggling with my sense of beauty here, having difficulties to
> decide if the following is a good or bad idea. A few comments about the
> following code with respect to good lisp style would be highly
> appreciated.
>
> I'm working with graphs (the mathematical structures) and wrote a couple
> of functions which all take a graph as it's first argument. To safe
> myself some keystrokes, I wrote a macro which takes a graph as argument,
> and provides curried functions of the graph-functions, also changing
> the name to avoid confusion (and to safe a few more keystrokes).
>
> Therefore instead of
>
> (progn
> (graph-add-vertex some-graph 4)
> (graph-delete-vertex some-graph 5))
>
> now I can do
>
> (with-graph (some-graph)
> (add-vertex 4)
> (delete-vertex 5)
>
> This comes in very handy when I have to do multiple operations on the
> same object. However, it feels strange to generate new symbols, so I
> wonder if there is a better way doing this kind of thing. Global
> variables would work, but that would break my attempt of doing things in
> a functional manner.

You can't do this in a functional manner, because functional languages
typically don't have macros (because they are ``dirty''), and the contents of
your WITH-GRAPH are imperative anyway.

> Thanks
> A
>
>
> (defgeneric graph-add-vertex (graph ...) ...)
>
> (defparameter *graph-functions*
> '(graph-add-vertex graph-delete-vertex graph-vertex-outdegree))
>
> (defun graph-flet-curry (graph function new-function)
> `(,new-function (&rest args)
> (apply #',function ,graph args)))
>
>
> ;; function `remove-symbol-prefix' alters a symbol by removing a prefix
> ;; (function definition not included for shortness)
>
> (defmacro with-graph ((graph) &body body)
> (flet ((graph-flet-curry (graph function new-function)
> `(,new-function (&rest args)
> (apply #',function ,graph args))))
> `(flet ,(loop for function in *graph-functions*
> collect (graph-flet-curry graph
> function
> (remove-symbol-prefix function
> 'graph)))
> ,@body)))

I don't see why your list of *graph-functions* can't be a structure
that associates each long name with a short name.

Also, that structure can describe the lambda lists better, so you
can generate more precise lambda lists than ``&rest args''.

Leave the ``atom smashing'' to physicists at CERN and elsewhere. :)

Your macro has some other problems. You are assuming that the parameter
GRAPH is a symbol. This is false, because someone (probably yourself)
will eventually write (with-graph (get-graph-from-somewhere ...) ...).
Or even: (with-graph (get-graph-and-cause-side-effect x) ...).

Another thing you may end up doing is this:
(with-graph (x) (lambda () (add-vertex 4))). YOu want to be sure that the
closure returned from this block of code correctly captures the graph which was
passed into it at the time the closure was made. You probably don't want the
lambda capturing the variable X (which won't actually happen if X isn't a
lexical).

Your macro should take a graph-designating expression, generate a block of code
which, when entered, evaluates that expression, storing the resulting
graph object in a hidden local variable (gensym). Your functions should
then reference that gensym. Take a look at how WITH-SLOTS works for instance.

(defun with-graph ((graph-designator) ,@body)
(let ((graph-var (gensym "GRAPH-")))
`(let ((,graph-var ,graph-designator))
(flet ... ))))

Thus ,graph-designator appears only once in your macro template. In all
the other places, you write ,graph-var wherever you need the graph.
The gensym will be substituted in those places.

Tamas K Papp

unread,
Dec 16, 2008, 7:47:20 AM12/16/08
to
On Tue, 16 Dec 2008 07:08:14 +0530, Madhu wrote:

> All you have done is switch the argument order. You get back your
> with-graph macro in this form:
>
> (defmacro with-graph (graph &body body) `(let ((*graph* graph)) ,@body))

Is this thread-safe? What if a similar expression is being evaluated in
another thread, but for a different graph?

Tamas

Madhu

unread,
Dec 16, 2008, 8:59:42 AM12/16/08
to

* Tamas K Papp <6qpmaoF...@mid.individual.net> :
Wrote on 16 Dec 2008 12:47:20 GMT:

Check your lisp implementation's documentation on threads: if it makes
special variables thread local, I assume it is safe.

--
Madhu

Alessio Stalla

unread,
Dec 16, 2008, 9:13:29 AM12/16/08
to

In most Lisp implementations I know of, local special bindings
(introduced by let) are per-thread.

Quite off-topic, but I have another doubt wrt specials that comes to
my mind now:

(defvar *var* 42)
(defun f (&optional *var*) (print *var*))

is (f) guaranteed to return 42 (provided no-one sets *var*, of
course), both in interpreted and compiled code?
is (let ((*var* 43)) (f)) guaranteed to return 43, both... well you
have understood, don't you? ;)

Alessio

Pascal Costanza

unread,
Dec 16, 2008, 9:21:15 AM12/16/08
to
Alessio Stalla wrote:
> On Dec 16, 1:47 pm, Tamas K Papp <tkp...@gmail.com> wrote:
>> On Tue, 16 Dec 2008 07:08:14 +0530, Madhu wrote:
>>> All you have done is switch the argument order. You get back your
>>> with-graph macro in this form:
>>> (defmacro with-graph (graph &body body) `(let ((*graph* graph)) ,@body))
>> Is this thread-safe? What if a similar expression is being evaluated in
>> another thread, but for a different graph?
>
> In most Lisp implementations I know of, local special bindings
> (introduced by let) are per-thread.
>
> Quite off-topic, but I have another doubt wrt specials that comes to
> my mind now:
>
> (defvar *var* 42)
> (defun f (&optional *var*) (print *var*))
>
> is (f) guaranteed to return 42 (provided no-one sets *var*, of
> course), both in interpreted and compiled code?

If nobody sets *var* and nobody rebinds it, yes.

> is (let ((*var* 43)) (f)) guaranteed to return 43, both... well you
> have understood, don't you? ;)

Yes, guaranteed to return 43.


Pascal

--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/

Joshua Taylor

unread,
Dec 16, 2008, 9:43:22 AM12/16/08
to

I think it was just a casual typo, but just to be clear, you'll only
get 42 and 43 if that optional also has a default value which happens
to be *var* as well. Otherwise you'll just get NIL.

CL-USER 1 > (defvar *var* 42)
*VAR*

CL-USER 2 > (defun f (&optional *var*) (print *var*))
F

CL-USER 3 > (f)
NIL ;; with no default value, *var* gets bound to NIL
NIL

CL-USER 4 > (let ((*var* 43)) (f))
NIL ;; with no default value, *var* gets bound to NIL
NIL

CL-USER 5 > (defun f2 (&optional (*var* *var*)) (print *var*))
F2

CL-USER 6 > (f2)
42 ;; but with default value *var*, *var* is bound to the same value
42

CL-USER 7 > (let ((*var* 43)) (f2))
43
43

//JT

Tamas K Papp

unread,
Dec 16, 2008, 11:33:02 AM12/16/08
to

Yes, in SBCL it is safe. Thanks for the clarification.

Tamas

Kaz Kylheku

unread,
Dec 16, 2008, 4:34:00 PM12/16/08
to
On 2008-12-16, Alessio Stalla <alessi...@gmail.com> wrote:
> On Dec 16, 1:47 pm, Tamas K Papp <tkp...@gmail.com> wrote:
>> On Tue, 16 Dec 2008 07:08:14 +0530, Madhu wrote:
>> > All you have done is switch the argument order. You get back your
>> > with-graph macro in this form:
>>
>> > (defmacro with-graph (graph &body body) `(let ((*graph* graph)) ,@body))
>>
>> Is this thread-safe?  What if a similar expression is being evaluated in
>> another thread, but for a different graph?
>
> In most Lisp implementations I know of, local special bindings
> (introduced by let) are per-thread.
>
> Quite off-topic, but I have another doubt wrt specials that comes to
> my mind now:
>
> (defvar *var* 42)
> (defun f (&optional *var*) (print *var*))
>
> is (f) guaranteed to return 42 (provided no-one sets *var*, of
> course), both in interpreted and compiled code?

(F) does not return 42 but NIL. *var* is locally bound, and not given
a value, so it is initialized to NIL.

There is nothing special about an &OPTIONAL parameter. It's just another local
variable.

The binding of an &OPTIONAL formal parameter takes place whether or not the
actual argument is specified. If the argument is not specified, then the
parameter receives the value NIL.

Thus if you want (F) to return 42, you have to specify that argument
value, i.e. (F 42).

> is (let ((*var* 43)) (f)) guaranteed to return 43, both... well you
> have understood, don't you? ;)

Nope, still NIL. See above.

Alessio Stalla

unread,
Dec 16, 2008, 6:19:21 PM12/16/08
to
On 16 Dic, 15:43, Joshua Taylor <joshuaaa...@gmail.com> wrote:
> I think it was just a casual typo, but just to be clear, you'll only
> get 42 and 43 if that optional also has a default value which happens
> to be *var* as well. Otherwise you'll just get NIL.

Duh, you're right; but it was not properly a typo, it was more of an
oversight. Actually my doubt came from the fact that I find myself
sometimes writing

(defun f (x y &optional (context *context*))
(let ((*context* context))
...code using *context*...))

which, as you have taught me, can be written simply as

(defun f (x y &optional (*context* *context*))
...code using *context*...)

So, thanks for the clarification, and sorry for going OT.

Alessio

Albert Krewinkel

unread,
Dec 16, 2008, 8:35:10 PM12/16/08
to

Cool. That was one of the things I worried about when I came up with
my question. So "functional" in my original question should have read
"thread safe". D'OH

Thanks for the help.

Albert Krewinkel

unread,
Dec 16, 2008, 8:47:08 PM12/16/08
to
Kaz Kylheku <kkyl...@gmail.com> writes:

> On 2008-12-16, Albert Krewinkel <krew...@gmx.net> wrote:

>> [lots of strange stuff]


>>
>> This comes in very handy when I have to do multiple operations on the
>> same object. However, it feels strange to generate new symbols, so I
>> wonder if there is a better way doing this kind of thing. Global
>> variables would work, but that would break my attempt of doing things in
>> a functional manner.
>
> You can't do this in a functional manner, because functional languages
> typically don't have macros (because they are ``dirty''), and the contents of
> your WITH-GRAPH are imperative anyway.

Right. I should have said thread-safe, as that's was what I really meant.

>> (defgeneric graph-add-vertex (graph ...) ...)
>>
>> (defparameter *graph-functions*
>> '(graph-add-vertex graph-delete-vertex graph-vertex-outdegree))
>>
>> (defun graph-flet-curry (graph function new-function)
>> `(,new-function (&rest args)
>> (apply #',function ,graph args)))
>>
>>
>> ;; function `remove-symbol-prefix' alters a symbol by removing a prefix
>> ;; (function definition not included for shortness)
>>
>> (defmacro with-graph ((graph) &body body)
>> (flet ((graph-flet-curry (graph function new-function)
>> `(,new-function (&rest args)
>> (apply #',function ,graph args))))
>> `(flet ,(loop for function in *graph-functions*
>> collect (graph-flet-curry graph
>> function
>> (remove-symbol-prefix function
>> 'graph)))
>> ,@body)))
>
> I don't see why your list of *graph-functions* can't be a structure
> that associates each long name with a short name.
>
> Also, that structure can describe the lambda lists better, so you
> can generate more precise lambda lists than ``&rest args''.
>
> Leave the ``atom smashing'' to physicists at CERN and elsewhere. :)

I will. Although, given the current state of the LHC: they won't be
doing that, for the next few month ;-)

> Your macro has some other problems. You are assuming that the parameter
> GRAPH is a symbol. This is false, because someone (probably yourself)
> will eventually write (with-graph (get-graph-from-somewhere ...) ...).
> Or even: (with-graph (get-graph-and-cause-side-effect x) ...).
>
> Another thing you may end up doing is this:
> (with-graph (x) (lambda () (add-vertex 4))). YOu want to be sure that the
> closure returned from this block of code correctly captures the graph which was
> passed into it at the time the closure was made. You probably don't want the
> lambda capturing the variable X (which won't actually happen if X isn't a
> lexical).
>
> Your macro should take a graph-designating expression, generate a block of code
> which, when entered, evaluates that expression, storing the resulting
> graph object in a hidden local variable (gensym). Your functions should
> then reference that gensym. Take a look at how WITH-SLOTS works for instance.
>
> (defun with-graph ((graph-designator) ,@body)
> (let ((graph-var (gensym "GRAPH-")))
> `(let ((,graph-var ,graph-designator))
> (flet ... ))))
>
> Thus ,graph-designator appears only once in your macro template. In all
> the other places, you write ,graph-var wherever you need the graph.
> The gensym will be substituted in those places.

Thanks a lot for the help.

0 new messages