fnwrap is a simple Common Lisp library that supports function wrapping. Function wrapping means wrapping additional code around an existing function, typically to print information about function calls, or pause when certain input values or return values are seen.
Lisp and Common Lisp have had function wrapping packages for years.
Popular packages are
advise, defadvice, and
fwrapper. Most are designed to be very flexible
and introduce as little overhead as possible.
They allow you to put wrappers around wrappers,
building up layers of additional functionality.
With that power comes complexity. fnwrap
in contrast was designed to be as simple and portable
as possible. In particular:
flet
and labels.The model of wrapping is very simple:
defwrapper macro.(fn-var . args), where
fn-var is a variable name.funcall or apply with fn-var
and the arguments passed to it.unwrap-functions.Here's a simple example, using the factorial function as
the original function. First, we define fact
Our definition includes
a declaration that prevents Lisp compilers from replacing
the recursive call with a more efficient iterative loop. That's
so we can see how recursion interacts with wrappers.
(defun fact (n) (declare (notinline fact)) (if (= n 1) 1 (* n (fact (1- n)))))
One use of wrappers is during debugging, when you want
to stop a function if it is passed certain argument values.
For example, here's the code to make fact pause,
using Lisp's break facility, whenever
it's passed a multiple of 5. We use defwrapper
to define the new version of fact, and
funcall to call the original code to do the
calculations.
(defwrapper fact (fn n)
(when (zerop (rem n 5))
(break "~S is a multiple of 5" n))
(funcall fn n))
defwrapper stores a new definition
for fact, but remembers the original definition.
If we call (fact 17), the wrapper is
passed the original fact function and 7. So
we get this output:
(FACT 7) Break: 15 is a multiple of 5 [1]...interact with Lisp, then continue... 5040
Another common use of wrapping is to trace a function but only for certain conditions, e.g., certain argument values. We have to be careful that our wrapper returns the correct value for all inputs. With recursive functions and printing, we also have to be careful to print the arguments before calling the stored definition, so that we don't end up with return values being printed before the arguments that generate them.
Here's a wrapper that only prints
those calls where fact is passed an odd number.
(defwrapper fact (fn n)
(when (oddp n)
(print (list 'fact n)))
(let ((result (funcall fn n)))
(when (oddp n)
(print (list 'result result)))
result))
Here's the output that we get:
> (FACT 7) (FACT 7) (FACT 5) (FACT 3) (FACT 1) (RESULT 1) (RESULT 6) (RESULT 120) (RESULT 5040) 5040
This isn't such great output, and it's a little tricky to write
code like this. Therefore, the fnwrap package includes
a print-trace macro to simplify writing
conditional tracing wrappers. A much better way to do trace
fact for odd arguments is:
(defwrapper fact (fn n) (print-trace (fact n) (funcall fn n) (oddp n)))
The syntax of print-trace is
(print-trace call-form calculation-form [test-form]).
If test-form is omitted, or evaluates to true, print-trace
will print the contents of call-form, then call and print
the values returned by calculation-form. call-form
can either be a function name, or a list beginning with a function name,
followed by zero or more expressions. The function name is printed, without
being evaluated, and then the expressions are evaluated and their return values are
printed.
Here's the output we get for the print-trace
version of fact:
> (fact 7)
FACT << 7
FACT << 5
FACT << 3
FACT << 1
FACT >> 1
FACT >> 6
FACT >> 120
FACT >> 5040
5040
To restore fact back to its former self,
use unwrap-functions.
> (unwrap-functions fact) > (fact 120) 668950291344...
unwrap-functions works like untrace.
You can specify one or more functions to unwrap. If you simply
say (unwrap-functions), then all wrapped functions
are unwrapped.
Here is a list of the functions and macros exported by fnwrap.
print-trace is a utility for printing trace-like
output, with indentation for recursive calls.
calculation-form and test-form
are expressions. test-form is evaluated first. If it is false,
calculation-form is evaluated and returned. Multiple values are returned,
if any. If test-form is true, the contents of call-form
are printed, then calculation-form is evaluated and its return
values printed, and then those values are returned. call-form
can be either a function name, or a list of the form
(name exp1 exp2 ...).
name is printed, but not evaluated. The expressions are evaluated
and the values returned are printed.Comments?
Send mail to Chris
Riesbeck.