From: thorne
Subject: Looping at toplevel?
Date: 
Message-ID: <86tzmnebgr.fsf@timbral.net>
[disclaimer: new to common lisp, and trying to figure out how macros work]

I am trying to run a macro that defines functions.  Instead of writing
out the call to the macro every time, i'd like to just loop through a
list calling the macro against each item in the list. so something like:

(defvar foo '(a b c d))
(loop for x in foo do (my-macro x))

Generating functions named a, b, c, and d...

The problem is, well, it doesn't work.  There is no error produced, but
if i then do--

(a)

--i get told there is no such function.

I'm not sure why, but i suspect that it is because the functions are
getting defined within the scope of `loop' (or `dolist') and vanish as
soon as the loop returns.  Is this the problem?  And if so (or not) is
there a good solution?

Thanks.

-- 
Theron Ttlåx

From: thorne
Subject: Re: Looping at toplevel?
Date: 
Message-ID: <86d4tbeaod.fsf@timbral.net>
thorne <······@timbral.net> writes:

Actually, as soon as i posted i thought of:

(eval 
 (cons 'progn 
       (loop for x in foo collect
	     (list 'deftag x))))

Which seems to work, but something tells me it's not idiomatic
code.... or is it?

-- 
Theron Ttlåx
From: Rob Warnock
Subject: Re: Looping at toplevel?
Date: 
Message-ID: <SJmdnUsg3OqJBf3anZ2dnUVZ_oWdnZ2d@speakeasy.net>
thorne <······@timbral.net> wrote:
+---------------
| Actually, as soon as i posted i thought of:
| (eval 
|  (cons 'progn 
|        (loop for x in foo collect
| 	     (list 'deftag x))))
| Which seems to work, but something tells me it's not idiomatic
| code.... or is it?
+---------------

Nope. "EVAL bad."  ;-}

I have no idea *why* you want to pollute your COMMON-LISP-USER package
namespace with a bunch of generated function names... but if you insist,
consider writing a macro which expands to the above code, e.g.:

    (defmacro deftags (&rest tags)
      `(progn
        ,(loop for tag in tags
	   collect `(deftag ,tag))))

A better, more idomatic way might be to create a *single* data
structure -- a hash table or an alist or a B-tree, whatever --
which maps your "tags" to anonymous functions which close over
the unique-per-function data they will need later. This can easily
be done without any macros at all!!

    > (defvar *tagged-functions* (make-hash-table))

    *TAGGED-FUNCTIONS*
    > (defun add-tagged-function (tag)
	(assert (symbolp tag) ()
	  "This version of the code works only with symbols, not ~s" tag)
	(setf (gethash tag *tagged-functions*)
	      (lambda ()
		(format t "Hi! My name is ~s~%" tag))))

    ADD-TAGGED-FUNCTION
    > (mapc #'add-tagged-function '(a b c d e f foo bar baz charlie mike))

    (A B C D E F FOO BAR BAZ CHARLIE MIKE)
    > (funcall (gethash 'charlie *tagged-functions*))
    Hi! My name is CHARLIE
    NIL
    > 

And you don't even need macros to make this convenient to use, e.g.:

    > (defun do-tags (&rest rest)
	;; Allow *either* a single list arg or a spread list of tags 
	(when (and (listp (first rest)) (endp (rest rest)))
	  (setf rest (first rest)))
	(dolist (tag rest)
	  (funcall (gethash tag *tagged-functions*))))

    DO-TAGS
    > (do-tags 'foo)
    Hi! My name is FOO
    NIL
    > (do-tags 'foo 'bar 'mike)
    Hi! My name is FOO
    Hi! My name is BAR
    Hi! My name is MIKE
    NIL
    > (do-tags '(baz charlie d c))
    Hi! My name is BAZ
    Hi! My name is CHARLIE
    Hi! My name is D
    Hi! My name is C
    NIL
    > (mapc #'do-tags (append '(foo mike) '(a b)))
    Hi! My name is FOO
    Hi! My name is MIKE
    Hi! My name is A
    Hi! My name is B
    (FOO MIKE A B)
    > 


-Rob

p.s. Exercise for the student: As it stands, this will fail:

    > (add-tagged-function "gorp")

    Error in function LISP::ASSERT-ERROR:
       This version of the code works only with symbols, not "gorp"
       [Condition of type SIMPLE-ERROR]

    Restarts:
      0: [CONTINUE] Retry assertion.
      1: [ABORT   ] Return to Top-Level.
    ...[etc.]...

Bonus question: Not only that, make it so that FOO, :FOO, "foo",
and "FOO" are all considered to be the "same" tag, regardless of
the current (READTABLE-CASE *READTABLE*).

-----
Rob Warnock			<····@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607
From: thorne
Subject: Re: Looping at toplevel?
Date: 
Message-ID: <86wsrj18v2.fsf@timbral.net>
····@rpw3.org (Rob Warnock) writes:

> thorne <······@timbral.net> wrote:
> +---------------
> | Actually, as soon as i posted i thought of:
> | (eval 
> |  (cons 'progn 
> |        (loop for x in foo collect
> | 	     (list 'deftag x))))
> | Which seems to work, but something tells me it's not idiomatic
> | code.... or is it?
> +---------------
>
> Nope. "EVAL bad."  ;-}

That's what i've heard, and a Google search reveals lots of people
saying, `don't use eval,' but they (the ones i've found) don't say why
not, and i'm not clear on what's wrong with eval...  Is there something
that talks about it online somewhere?

(The rest of your reply (and Ken's) are interesting and helpful and
thank you--and i have to play around with it for a while...)

-- 
Theron Ttlåx
From: JohnW
Subject: Re: Looping at toplevel?
Date: 
Message-ID: <c6585b57-191e-4f62-9b93-fb9a00231d19@i12g2000prf.googlegroups.com>
On Dec 13, 1:06 am, thorne <······@timbral.net> wrote:
> > Nope. "EVAL bad."  ;-}
>
> That's what i've heard, and a Google search reveals lots of people
> saying, `don't use eval,' but they (the ones i've found) don't say why
> not, and i'm not clear on what's wrong with eval...  Is there something
> that talks about it online somewhere?

EVAL is not "bad", any more than GOTO or multiple exit points in a
procedure are bad.  All of them are legitimate practices that in some
cases lead to better implementations than avoiding them would.  It's
the "some cases" part that's tricky.

Perhaps some people harp on EVAL because newbies turn to it quicker
than they should -- when often there is a much better solution.  One
reason to avoid it is that with non-interpreted Lisps, it invokes the
entire mechanism of the parser/compiler.  This can trash performance
if it gets called often.

For example, lets say I'm writing a function that gets passed two
numbers and needs to return something that will be evaluated later to
find the result. (Let's assume that this "later determination" is a
requirement).  Here's a naive implementation based on EVAL:

  (defun create-adder (x y)
     (list '+ x y))

and a later usage:

  (eval (create-adder 10 20))

It seems to work great, but it's horribly expensive compared to the
intended alternative: closures.

  (defun create-adder (x y)
    (lambda () (+ x y)))

  (funcall (create-adder 10 20))

With closures, the code inside the lambda is pre-compiled, so you can
call CREATE-ADDER inside a loop and see little degradation of
performance.  Think of the closure as a function that binds its
arguments inside the body, rather than to a parameter list.

Of course, in some cases closures have their own issues, like the lack
of compiler optimization when the types of X and Y must remain
generic.  With EVAL and COMPILE you get to benefit from all the smart
compilation logic that comes with a specially coded solution (in this
case, you're doing the coding by pasting the form to pass to EVAL).

Personally, I find the following to be an acceptable rule of thumb:

 1. If the EVAL happens only once, and I can't determine the
information at compile-time, it's not too harmful -- and might lead to
the fastest resulting code.

 2. If I'm writing a closure or code-walker solution simply for the
sake of avoiding EVAL, and no other reason, I'm just being a damn
purist.

 3. If my call to EVAL might occur inside a loop (and by loop, I refer
to any algorithm with greater complexity than O(1)), I need to
seriously think over why I'm doing it that way.

So far, none of my code uses EVAL.  I was using it in a few places,
but then found another way that uses 1/1000 as many cons cells.  It's
not because I don't like EVAL particularly; I just rarely need a
compile-time solution to solve run-time problems.

John
From: Maciej Katafiasz
Subject: Re: Looping at toplevel?
Date: 
Message-ID: <fjqmeu$lrf$1@news.net.uni-c.dk>
Den Wed, 12 Dec 2007 16:49:06 -0700 skrev thorne:

> Actually, as soon as i posted i thought of:
> 
> (eval
>  (cons 'progn
>        (loop for x in foo collect
> 	     (list 'deftag x))))
> 
> Which seems to work, but something tells me it's not idiomatic code....
> or is it?

It's not. There are a couple of problems:

1) you're carving forms out by hand. That's what God made the backquote 
for. In case you're reading a book and tried that before backquote was 
explained, just look a few pages ahead, it's gotta be explained next. So, 
that gives

(eval `(progn ,@(loop for x in foo collect `(deftag x)))) ;; Untested

2) You're using EVAL. That's usually a mistake in and of itself. However, 
in your case, the problem is that you're trying to call a macro (which 
has compile-time effects) inside a DO clause, which is only executed at 
runtime. That can't work, as at the time the macro will finally be 
called, there will be no more environment for it to operate in. To do 
what you want, you'd need another macro to expand to several calls to 
MY-MACRO. This way it's all kept to compile-time:

(defvar foo '(a b c d))
(defun my-macro (x)
  `(defun ,x ()))

(defmacro my-expander (symbols)
  `(progn
     ,@(loop for x in (eval symbols) collect (my-macro x))))

(my-expander foo)

What you've chosen to do is actually pretty tricky, because you're
inherently mixing compile-time (macro) and run-time (value of FOO)
effects, so there's no escaping EVAL. But at least the damage is
localised.

Cheers,
Maciej
From: Ken Tilton
Subject: Re: Looping at toplevel?
Date: 
Message-ID: <4760a643$0$5980$607ed4bc@cv.net>
thorne wrote:
> [disclaimer: new to common lisp, and trying to figure out how macros work]
> 
> I am trying to run a macro that defines functions.  Instead of writing
> out the call to the macro every time, i'd like to just loop through a
> list calling the macro against each item in the list. so something like:
> 
> (defvar foo '(a b c d))
> (loop for x in foo do (my-macro x))

Try (print (macroexpand '(my-macro x))) in there. Then put a print 
statement in my-macro to print out the value of the input parameter, 
such that it runs at macro-expansion time (not as part of the expansion 
produced) and put the whole sheband in a function and then compile the 
function. The result should help you get a feel for the issues (which 
are all about macros, not the other things you are contemplating.)

kt

-- 
http://www.theoryyalgebra.com/

"In the morning, hear the Way;
  in the evening, die content!"
                     -- Confucius