A lightweight and portable system for advising functions in Common Lisp.
CL-ADVICE
implements a new function type which carries with it slots for
before, around, and after advice. Functions can be defined as advisable,
existing functions not within a locked package can be converted to advisable
functions, and advisable functions can be converted to regular functions.
Pieces of advice are functions which get called before, after, or around the main function. Generally speaking, its a good idea to define advice functions as named functions and add it as a symbol and not a function object. This makes removing advice easier, and allows the advised function to use the new definition should the advice function be recompiled. Additionally, while installing anonymous functions as advice is allowed, removing anonymous function advice requires knowing where in the advice list it lies, or holding a reference to the anonymous function object.
Before and After advice must have an argument list that is compatible with the main function. Two argument lists are considered compatible if they can both be applied to the same arguments. These advice functions are looped through and called in order.
Around advice must take the next function to be called as its first argument, and all following arguments must be compatible with the main functions argument list. Around advice is unique in that it has control over whether or not the next function will be called. The next function may be the main function, or the next piece of around advice.
This system is used primarily through the functions make-advisable
,
defun-advisable
, add-advice
, replace-advice
, and remove-advice
. For
more information, see the docstrings of the functions and macros exported in
package.lisp
.
When making functions advisable, the original function object is wrapped in a
funcallable object which has a dispatch function as its main function. This
conversion is done through the function make-advisable
. By default,
functions are converted to be advisable implicitly, through the function
ensure-advisable-function
. This is controlled by the dynamic variable
*allow-implicit-conversion*
, and can be enabled or disabled for a body of
code through the macro with-implicit-conversion
. If conversion
This function creates an advisable function object and, if the function to make advisable is a symbol, rebinds the symbol-function to this new function. In addition it defines a dispatcher function for the advisable function object.
When defining the dispatch function all care will be taken to preserve the
original argument list, however this isnt guaranteed. The function
make-advisable
has a compiler macro defined for it which will define the
dispatcher function with correct arguments if they are provided. However a
compiler macro may not always be called. For this reason the argument
force-use-arguments
is provided which forces generation of a dispach
function with the correct argument list by using eval
.
The macro defun-advisable
copies existing advice if and only if the
function has the same argument list (as compared by equal
).
We can implement trace
in terms of :around
advice like so:
(defpackage :tracer
(:use :cl :cl-advice))
(in-package :tracer)
(defun make-simple-tracer (&optional (sym 'unknown-function))
(let ((inc 0))
(lambda (next-fn &rest args)
(let ((string (make-string inc :initial-element #\space)))
(format t "~&~A~A: Calling ~A with arguments ~A~%"
string inc sym args)
(incf inc)
(let ((result (apply next-fn args)))
(decf inc)
(format t "~&~A~A: ~A returned ~A~%"
string inc sym result)
result)))))
(defun-advisable fib (n)
(if (< n 2)
n
(+ (fib (- n 1)) (fib (- n 2)))))
(add-advice :around 'fib (make-simple-tracer 'fib))
The result of this is that when fib
is called, the following will be
printed to standard output:
TRACER> (fib 1)
0: Calling FIB with arguments (1)
0: FIB returned 1
1
TRACER> (fib 5)
0: Calling FIB with arguments (5)
1: Calling FIB with arguments (4)
2: Calling FIB with arguments (3)
3: Calling FIB with arguments (2)
4: Calling FIB with arguments (1)
4: FIB returned 1
4: Calling FIB with arguments (0)
4: FIB returned 0
3: FIB returned 1
3: Calling FIB with arguments (1)
3: FIB returned 1
2: FIB returned 2
2: Calling FIB with arguments (2)
3: Calling FIB with arguments (1)
3: FIB returned 1
3: Calling FIB with arguments (0)
3: FIB returned 0
2: FIB returned 1
1: FIB returned 3
1: Calling FIB with arguments (3)
2: Calling FIB with arguments (2)
3: Calling FIB with arguments (1)
3: FIB returned 1
3: Calling FIB with arguments (0)
3: FIB returned 0
2: FIB returned 1
2: Calling FIB with arguments (1)
2: FIB returned 1
1: FIB returned 2
0: FIB returned 5
5 (3 bits, #x5, #o5, #b101)
advisable-function-p function
Returns T if function is an advisable function.
- Arguments and Values
- function - a object
make-advisable symbol &key arguments force-use-arguments
Converts a function to an advisable function. If symbol is a symbol, then
the function denoted by it is converted and the symbol-function
of symbol
is set to the new advisable function. If symbol is a function object it is
converted to an advisable function and returned.
When arguments is provided and the call is being compiled, a compiler macro will generate a dispatcher function with this argument list. If the call is not being compiled or the compiler macro is not triggered then a generic dispatcher argument list is used.
When force-use-arguments is T and the compiler macro is not triggered,
eval
is used to generate a dispatcher function that uses arguments for
its argument list.
- Arguments and Values
- symbol - a symbol denoting a function or a function object
- arguments - the argument list of symbol
- force-use-arguments - When T force the usage of arguments for the advisable function dispatcher function.
make-unadvisable symbol
Convert an advisable function to be unadvisable. If symbol is a symbol then
the function referred to by symbol is converted to be unadvisable and
symbol has its symbol-function
rebound to this function. If symbol is a
function object the unadvisable function is returned.
- Arguments and Values
- symbol - a symbol or function to convert to be unadvisable
ensure-advisable-function symbol &optional arguments force-use-arguments
Returns an advisable function or signals an error. If symbol denotes an
unadvisable function and *allow-implicit-conversion*
is T then symbol is
converted via make-advisable
. If *allow-implicit-conversion*
is NIL, then
an error of type implicit-conversion-to-advisable-function
is signalled
with two restarts established around it. These restarts are allow-conversion
,
which converts the function, and return-value
, which takes a value to
return. When called interactively the return-value restart reads and
evaluates a value from the user.
When implicitly converting a function to be advisable, arguments and
force-use-arguments are passed to make-advisable
.
- Arguments and Values
- symbol - a symbol or function object
- arguments - a argument list
- force-use-arguments - a true or false value
ensure-unadvisable-function symbol
Calls make-unadvisable
on symbol and return an unadvisable function.
- Arguments and Values
- symbol - a symbol or function object
with-implicit-conversion (allow-or-not &optional abort-on-implicit-conversion return-on-abort) &body body
Binds the variable *allow-implicit-conversion*
to T or NIL based upon
whether allow-or-not is eql
to :allowed
, where allow-or-not is
evaluated at runtime. If abort-on-implicit-conversion is true (at
macroexpansion time) then if implicit-conversion-to-advisable-function
is
signalled then control leaves body immediately, and return-on-abort is
returned.
advisable-lambda argslist &body body
Functions the same as lambda
, but returns an advisable function object.
- Arguments and Values
- argslist - a function argument list
- body - A function body
defun-advisable name argslist &body body
Functions the same as defun
but defines an advisable function.
- Arguments and Values
- name - an unquoted symbol denoting the name for the function
- argslist - a function argument list
- body - A function body
Variable with the default value of T. When T, allow
ensure-advisable-function
to implicitly convert unadvisable functions to be
advisable. When NIL, signal an error when attempting to implicitly convert an
unadvisable function.
add-advice where function advice-function &key allow-duplicates test from-end
Advise function with advice-function. If allow-duplicates is NIL, test for duplicates using test.
- Arguments and Values
- where - a keyword denoting the kind of advice advice-function
is. Must be one of
:before
,:after
, or:around
. - function - a symbol or function object
- advice-function - the advice function to install.
- allow-duplicates - a true or false value. When true duplicate advice is allowed.
- test - a function to compare pieces of advice. Used when allow-duplicates is NIL
- from-end - Determines where to add the advice in its appropriate advice list. When T add the advice at the end of the advice list, when NIL add it at the beginning.
- where - a keyword denoting the kind of advice advice-function
is. Must be one of
replace-advice where function old-advice new-advice &key test if-not-found
Replace a piece of advice.
- Arguments and Values
- where - a symbol denoting the type of advice to replace, one of
:before
,:around
, or:after
- function - the function to replace the advice for
- old-advice - the advice to replace
- new-advice - the advice to replace old-advice with
- test - a function to compare advice
- if-not-found - A keyword denoting what to do if old-advice isnt
found. Must be one of
:prepend
,:append
, or NIL.
- where - a symbol denoting the type of advice to replace, one of
list-advice fn &key type print
Lists advice for fn.
- Arguments and Values
- fn - a function to print advice for
- type - a keyword denoting what kind of advice to list. Must be one of
:all
,:before
,:around
, or:after
. - print - when true print all advice to standard output.
remove-advice type fn advice &key test
Remove advice from fn.
- Arguments and Values
- type - a keyword denoting which advice list to remove advice
from. Must be one of
:before
,:around
, or:after
. - fn - a symbol or function object
- advice - the piece of advice to remove. Must be a symbol, function, or
the keyword
:all
. - test - a function to compare pieces of advice
- type - a keyword denoting which advice list to remove advice
from. Must be one of
remove-nth-advice type fn nth
Remove the nth element of advice from type advice list for fn.
- Arguments and Values
- type - a keyword denoting which advice list to remove the nth
from. Must be one of
:before
,:after
, or:around
. - fn - the function to remove the advice from
- nth - the element to remove
- type - a keyword denoting which advice list to remove the nth
from. Must be one of