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).
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.
* Albert Krewinkel <m2prjs2abc....@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.")
Madhu <enom...@meer.net> writes: > * Albert Krewinkel <m2prjs2abc....@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.")
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.
|> * Albert Krewinkel <m2prjs2abc....@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]
> 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).
> 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.
> ;; 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.
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 <6qpmaoFdv2e...@mid.individual.net> : Wrote on 16 Dec 2008 12:47:20 GMT:
| 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?
Check your lisp implementation's documentation on threads: if it makes special variables thread local, I assume it is safe.
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:
> 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? is (let ((*var* 43)) (f)) guaranteed to return 43, both... well you have understood, don't you? ;)
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:
> 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:
> 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
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
On Tue, 16 Dec 2008 19:29:42 +0530, Madhu wrote: > * Tamas K Papp <6qpmaoFdv2e...@mid.individual.net> : Wrote on 16 Dec > 2008 12:47:20 GMT:
> | 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?
> Check your lisp implementation's documentation on threads: if it makes > special variables thread local, I assume it is safe.
Yes, in SBCL it is safe. Thanks for the clarification.
> 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:
> 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? ;)
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.
> On Tue, 16 Dec 2008 19:29:42 +0530, Madhu wrote:
>> * Tamas K Papp <6qpmaoFdv2e...@mid.individual.net> : Wrote on 16 Dec >> 2008 12:47:20 GMT:
>> | 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?
>> Check your lisp implementation's documentation on threads: if it makes >> special variables thread local, I assume it is safe.
> Yes, in SBCL it is safe. Thanks for the clarification.
> Tamas
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
Kaz Kylheku <kkylh...@gmail.com> writes: > On 2008-12-16, Albert Krewinkel <krewin...@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.
> 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.
> 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.