cl-advice
2023-02-15
Portable advice for Common Lisp
A lightweight and portable system for advising functions in Common Lisp.
1Description
CL-ADVICE
implements a new function type which carries with it slots forbefore, around, and after advice. Functions can be defined as advisable,existing functions not within a locked package can be converted to advisablefunctions, and advisable functions can be converted to regular functions.1.1Types of Advice
Pieces of advice are functions which get called before, after, or around themain function. Generally speaking, its a good idea to define advice functionsas named functions and add it as a symbol and not a function object. Thismakes removing advice easier, and allows the advised function to use the newdefinition should the advice function be recompiled. Additionally, whileinstalling anonymous functions as advice is allowed, removing anonymousfunction advice requires knowing where in the advice list it lies, or holdinga reference to the anonymous function object.1.1.1Before and After Advice
Before and After advice must have an argument list that is compatible withthe main function. Two argument lists are considered compatible if they canboth be applied to the same arguments. These advice functions are loopedthrough and called in order.1.1.2Around Advice
Around advice must take the next function to be called as its firstargument, and all following arguments must be compatible with the mainfunctions argument list. Around advice is unique in that it has control overwhether or not the next function will be called. The next function may bethe main function, or the next piece of around advice.2Usage
This system is used primarily through the functionsmake-advisable
,defun-advisable
, add-advice
, replace-advice
, and remove-advice
. Formore information, see the docstrings of the functions and macros exported inpackage.lisp
.2.1Making Functions Advisable
When making functions advisable, the original function object is wrapped in afuncallable object which has a dispatch function as its main function. Thisconversion is done through the functionmake-advisable
. By default,functions are converted to be advisable implicitly, through the functionensure-advisable-function
. This is controlled by the dynamic variable*allow-implicit-conversion*
, and can be enabled or disabled for a body ofcode through the macro with-implicit-conversion
. If conversion2.1.1MAKE-ADVISABLE
This function creates an advisable function object and, if the function tomake advisable is a symbol, rebinds the symbol-function to this newfunction. In addition it defines a dispatcher function for the advisablefunction object. When defining the dispatch function all care will be taken to preserve theoriginal argument list, however this isnt guaranteed. The functionmake-advisable
has a compiler macro defined for it which will define thedispatcher function with correct arguments if they are provided. However acompiler macro may not always be called. For this reason the argumentforce-use-arguments
is provided which forces generation of a dispachfunction with the correct argument list by using eval
.2.2Redefining functions
The macrodefun-advisable
copies existing advice if and only if thefunction has the same argument list (as compared by equal
).2.3Example: TRACE
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)
3Documentation
3.1Function ADVISABLE-FUNCTION-P
advisable-function-p /function/Returns T if function is an advisable function.
- Arguments and Values
- function - a object
3.2Function MAKE-ADVISABLE
make-advisable /symbol/ &key arguments force-use-argumentsConverts 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 theadvisable function dispatcher function.
3.3Function MAKE-UNADVISABLE
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
3.4Function ENSURE-ADVISABLE-FUNCTION
ensure-advisable-function /symbol/ &optional arguments force-use-argumentsReturns 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
3.5Function ENSURE-UNADVISABLE-FUNCTION
ensure-unadvisable-function /symbol/Calls make-unadvisable
on symbol and return an unadvisable function.
- Arguments and Values
- symbol - a symbol or function object
3.6Macro WITH-IMPLICIT-CONVERSION
with-implicit-conversion (allow-or-not &optional abort-on-implicit-conversion return-on-abort) &body bodyBinds the variable *allow-implicit-conversion*
to T or NIL based uponwhether allow-or-not is eql
to :allowed
, where allow-or-not isevaluated at runtime. If abort-on-implicit-conversion is true (atmacroexpansion time) then if implicit-conversion-to-advisable-function
issignalled then control leaves body immediately, and return-on-abort isreturned.3.7Macro ADVISABLE-LAMBDA
advisable-lambda /argslist/ &body bodyFunctions the same as lambda
, but returns an advisable function object.
- Arguments and Values
- argslist - a function argument list
- body - A function body
3.8Macro DEFUN-ADVISABLE
defun-advisable /name argslist/ &body bodyFunctions 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
3.9Dynamic Variable *ALLOW-IMPLICIT-CONVERSION*
Variable with the default value of T. When T, allowensure-advisable-function
to implicitly convert unadvisable functions to beadvisable. When NIL, signal an error when attempting to implicitly convert anunadvisable function. 3.10Function ADD-ADVICE
add-advice /where function advice-function/ &key allow-duplicates test from-endAdvise 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-functionis. 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 isallowed.
- test - a function to compare pieces of advice. Used whenallow-duplicates is NIL
- from-end - Determines where to add the advice in its appropriate advicelist. When T add the advice at the end of the advice list, when NIL addit at the beginning.
- where - a keyword denoting the kind of advice advice-functionis. Must be one of
3.11Function REPLACE-ADVICE
replace-advice /where function old-advice new-advice/ &key test if-not-foundReplace 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 isntfound. Must be one of
:prepend
,:append
, or NIL.
- where - a symbol denoting the type of advice to replace, one of
3.12Function LIST-ADVICE
list-advice /fn/ &key type printLists 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.
3.13Function REMOVE-ADVICE
remove-advice /type fn advice/ &key testRemove advice from fn.
- Arguments and Values
- type - a keyword denoting which advice list to remove advicefrom. 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, orthe keyword
:all
. - test - a function to compare pieces of advice
- type - a keyword denoting which advice list to remove advicefrom. Must be one of
3.14Function REMOVE-NTH-ADVICE
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 nthfrom. 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 nthfrom. Must be one of