From: Gregory Novak
Subject: Macroexpansion and explicitly calling eval
Date: 
Message-ID: <m23b996zmn.fsf@ucolick.org>
I have a general question about macroexpansion and evaluation.  The
specific context is that I'm trying to programmatically trace a bunch
of functions.  I want to be able to write 
(trace-em-all (function-that-returns-names-of-functions))
and have that generate code of the form
(trace f1 ..some options)
(trace f2 ..some other options) 
...

Just doing
(dolist (fn (function-that-returns-names-of-functions))
  (trace fn options))
won't work, because trace thinks it's supposed to trace fn, not the
value of fn.

So it looks like I need to generate a bunch of separate calls to
trace, ie, I need a macro.

I tried:
(defmacro trace-em-all (expr)
  (let ((fn-names expr))
    `(progn ,@(mapcar #'(lambda (fn) `(trace fn))
                      fn-names))))
thinking that then expr would be evaluated at macroexpansion time, but
of course its value was the form itself, not the result of evaluating
the form.

Anything like this:
(defmacro trace-em-all (expr)
  `(let ((fn-names ,expr))
      (dolist (fn fn-names)
          (trace fn))))
doesn't work either, because I still don't have the actual list of
function names until run-time, and therefore trace still thinks its
trying to trace a function called fn.

The only way I've found to get this to work is this:
(defmacro trace-em-all (expr)
  (let ((fn-names (eval expr)))
    `(progn ,@(mapcar #'(lambda (fn) `(trace fn))
                      fn-names))))

Given the strident admonitions against explicitly calling eval, I
thought I would check here to see if someone comes up with a better
way to do it.

BTW, I'm using SBCL if that matters.
Thanks!
Greg

From: Ken Tilton
Subject: Re: Macroexpansion and explicitly calling eval
Date: 
Message-ID: <YNy0h.247$td3.74@newsfe08.lga>
Gregory Novak wrote:
> I have a general question about macroexpansion and evaluation.  The
> specific context is that I'm trying to programmatically trace a bunch
> of functions.  I want to be able to write 
> (trace-em-all (function-that-returns-names-of-functions))
> and have that generate code of the form
> (trace f1 ..some options)
> (trace f2 ..some other options) 
> ...
> 
> Just doing
> (dolist (fn (function-that-returns-names-of-functions))
>   (trace fn options))
> won't work, because trace thinks it's supposed to trace fn, not the
> value of fn.
> 
> So it looks like I need to generate a bunch of separate calls to
> trace, ie, I need a macro.
> 
> I tried:
> (defmacro trace-em-all (expr)
>   (let ((fn-names expr))
>     `(progn ,@(mapcar #'(lambda (fn) `(trace fn))
>                       fn-names))))
> thinking that then expr would be evaluated at macroexpansion time, but
> of course its value was the form itself, not the result of evaluating
> the form.
> 
> Anything like this:
> (defmacro trace-em-all (expr)
>   `(let ((fn-names ,expr))
>       (dolist (fn fn-names)
>           (trace fn))))
> doesn't work either, because I still don't have the actual list of
> function names until run-time, and therefore trace still thinks its
> trying to trace a function called fn.
> 
> The only way I've found to get this to work is this:
> (defmacro trace-em-all (expr)
>   (let ((fn-names (eval expr)))
>     `(progn ,@(mapcar #'(lambda (fn) `(trace fn))
>                       fn-names))))

That works? I would expect:

   (progn (trace fn)(trace fn)(trace fn)....)

The fix would be `(trace ,fn)

Anyway, one thing you can try is macroexpanding a trace form to see what 
your implementation is doing. With ACL and (trace x) as the target I get:

(do ((excl::names '(x) (cdr excl::names)))
     ((null excl::names) (excl:ftrace 'x) '(x))
   (fdefinition (car excl::names)))

So in ACL you could (mapcar 'excl:ftrace (funcs-to-trace)).

Finally, if your func-list function has zero args or only literal args 
such as t or :x or 42, you could:

(defmacro trace-all (generator &rest gen-args)
    (let ((fnames (apply generator gen-args)))
       ...

Of course that is pretty close to eval.

Most of all I am puzzled by what you are actually doing. Are you hoping 
this will work at runtime? ie, the function that returns the functions 
to trace will be invoked at runtime to control what you are tracing? 
Since the macro does not expand at runtime, that will not work. You 
would have to expand your implementation's trace and hopefully find a 
function behind the scenes you can call.

kt

-- 
Cells: http://common-lisp.net/project/cells/

"I'll say I'm losing my grip, and it feels terrific."
    -- Smiling husband to scowling wife, New Yorker cartoon
From: Pascal Bourguignon
Subject: Re: Macroexpansion and explicitly calling eval
Date: 
Message-ID: <87hcxpntl5.fsf@thalassa.informatimago.com>
Gregory Novak <·····@ucolick.org> writes:
> The only way I've found to get this to work is this:
> (defmacro trace-em-all (expr)
>   (let ((fn-names (eval expr)))
>     `(progn ,@(mapcar #'(lambda (fn) `(trace fn))
>                       fn-names))))

TRACE takes several functions.

(DEFUN trace-em-all (list-of-function-names) 
  (funcall (compile nil `(lambda () (trace ,@list-of-function-names)))))

But personnaly, I see no inconvenient in writing:

(DEFUN trace-em-all (list-of-function-names) 
  (eval `(trace ,@list-of-function-names)))


(trace-em-all
  (let ((funs '())
        (p (find-package "SWANK-BACKEND")))
     (do-external-symbols (s p)
         (when (and (fboundp s)  (eq (symbol-package s) p)
                    (not (macro-function s)) (not (special-operator-p s)))
           (push s funs)))
     funs))

> Given the strident admonitions against explicitly calling eval, I
> thought I would check here to see if someone comes up with a better
> way to do it.


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
You never feed me.
Perhaps I'll sleep on your face.
That will sure show you.
From: Gregory Novak
Subject: Re: Macroexpansion and explicitly calling eval
Date: 
Message-ID: <m21wosdf20.fsf@ucolick.org>
Pascal Bourguignon <···@informatimago.com> writes:
> TRACE takes several functions.

I know.  The idea is to give different arguments for each call to
trace, like this:

(trace foo :print (push 'foo called-functions))

Ken Tilton <·········@gmail.com> writes:
> That works? I would expect:
>
>    (progn (trace fn)(trace fn)(trace fn)....)
>
> The fix would be `(trace ,fn)

Yes, right, sorry about writing code off the cuff.

Ken Tilton <·········@gmail.com> writes:
> Most of all I am puzzled by what you are actually doing. 

Let me first say that although I am interested in solving this
particular problem, I've run up against a similar class of problems in
the past (that is, where I want to generate code, so I'm writing a
macro, but the code I want to generate depends not just on the forms
that are passed as arguments, but on their values).  So even if
there's a way to sidestep the issue for my "trace" problem, I'm still
interested in problems of the same type.

What I'm trying to do is actually reasonably close to what Pascal has
written here:

Pascal Bourguignon <···@informatimago.com> writes:
> (trace-em-all
>   (let ((funs '())
>         (p (find-package "SWANK-BACKEND")))
>      (do-external-symbols (s p)
>          (when (and (fboundp s)  (eq (symbol-package s) p)
>                     (not (macro-function s)) (not (special-operator-p s)))
>            (push s funs)))
>      funs))

What I want is to find all defined functions in the current package
whose name starts with 'test-', then run some code (usually
'(test-all)' or something to that effect), and then print some
messages if any of those functions are not called.  That is, I want to
write something like this:

(print-message-unless-all-test-functions-are-called
  (test-all))

and have it expand into something like:

(let ((called-functions nil))
  (trace test-1 :print (unless (member 'test-1 called-functions)
                           (push 'test-1 called-functions)))
  (trace test-2 :print (unless (member 'test-2 called-functions)
                           (push 'test-2 called-functions)))
  ...
  (test-all)
  (unless (member 'test-1 called-functions) 
     (print "Test-1 not called!"))
  (unless (member 'test-1 called-functions) 
     (print "Test-1 not called!"))
  ...
  (untrace test-1)
  (untrace test-1)
  ...)

On a related note, I looked around but didn't find any way to advise
functions (which is all I'm using the trace facility for here).  What
do people do here?  CLOS :before :after and :around?  Implementation
specific?  Or is it just not done?

Ken Tilton <·········@gmail.com> writes:
> Are you hoping this will work at runtime? ie, the function that
> returns the functions to trace will be invoked at runtime to control
> what you are tracing? 

I expect to type it in at the REPL, so it's all going on at the same
time.

Greg
From: Ken Tilton
Subject: Re: Macroexpansion and explicitly calling eval
Date: 
Message-ID: <3gH0h.227$uN5.205@newsfe09.lga>
Gregory Novak wrote:
> Pascal Bourguignon <···@informatimago.com> writes:
> 
>>TRACE takes several functions.
> 
> 
> I know.  The idea is to give different arguments for each call to
> trace, like this:
> 
> (trace foo :print (push 'foo called-functions))
> 
> Ken Tilton <·········@gmail.com> writes:
> 
>>That works? I would expect:
>>
>>   (progn (trace fn)(trace fn)(trace fn)....)
>>
>>The fix would be `(trace ,fn)
> 
> 
> Yes, right, sorry about writing code off the cuff.
> 
> Ken Tilton <·········@gmail.com> writes:
> 
>>Most of all I am puzzled by what you are actually doing. 
> 
> 
> Let me first say that although I am interested in solving this
> particular problem, I've run up against a similar class of problems in
> the past (that is, where I want to generate code, so I'm writing a
> macro, but the code I want to generate depends not just on the forms
> that are passed as arguments, but on their values).

Ah, but that means you want to write a function, not a macro, since 
macros eat code, not runtime values.

So it /does/ come down to cases, even though you say "it happens a lot, 
so I do not want to discuss cases". The "happens a lot" suggests to me 
you are as a rule using macros where they are not really necessary, 
which is fine until you need to apply or funcall them. (And, yes, I find 
myself wondering why TRACE is a macro. <g>) So TheRealProblem is using 
macros unnecessarily. Maybe? We would have to look at the cases to be 
sure. :)

kt

-- 
Cells: http://common-lisp.net/project/cells/

"I'll say I'm losing my grip, and it feels terrific."
    -- Smiling husband to scowling wife, New Yorker cartoon
From: Pascal Bourguignon
Subject: Re: Macroexpansion and explicitly calling eval
Date: 
Message-ID: <871woso5zh.fsf@thalassa.informatimago.com>
Gregory Novak <·····@ucolick.org> writes:

> What I want is to find all defined functions in the current package
> whose name starts with 'test-', then run some code (usually
> '(test-all)' or something to that effect), and then print some
> messages if any of those functions are not called.  That is, I want to
> write something like this:

You want a coverage profiler.

We could advise functions, but this wouldn't take into account the
functions that have been inlined.   A good coverage profiler would.

For example, one of these profilers might be useful:

http://www-cgi.cs.cmu.edu/afs/cs/project/ai-repository/ai/lang/lisp/code/tools/metering/0.html


http://www.google.com/codesearch?q=+lang:lisp+profiler+show:rFRAa8p7E8k:MfFDB8kuzNE:zNaNR4YJPK4&sa=N&cd=7&ct=rc&cs_p=http://christian.jullien.free.fr/downloads/openlisp-8.1.0-Linux-parisc64.tar.gz&cs_f=./lib/profile.lsp#a0


In SBCL: sb-prof



(Commercial implementations certainly have even better profilers).

-- 
__Pascal_Bourguignon__               _  Software patents are endangering
()  ASCII ribbon against html email (o_ the computer industry all around
/\  1962:DO20I=1.100                //\ the world http://lpf.ai.mit.edu/
    2001:my($f)=`fortune`;          V_/   http://petition.eurolinux.org/