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:

The model of wrapping is very simple:

Example

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.

Reference Section

Here is a list of the functions and macros exported by fnwrap.

(defwrapper name arg-list exp1 exp2 ...)
This macro redefines the function name to have the given arguments and body. The "core" code for name is saved. If name is already wrapped when this is called, the new wrapper replaces the older wrapper. The core code remains unchanged.
(unwrap-functions name1 name2 ...)
This macro restores the core code for the functions named. If no names are given, it restores the core code for all wrapped functions.
(print-trace call-form calculation-form [test-form])
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.