ToC | DocOverview | CGDoc | RelNotes | FAQ | Index | PermutedIndex
| Allegro CL version 10.1 Unrevised from 10.0 to 10.1. 10.0 version

# Fwrapping and Advice

This document contains the following sections:

1.0 The Fwrap Facility
1.1 Background
2.0 The Def-fwrapper/fwrap Facility
2.1 Macroexpansion and Compilation of Fwrappers
3.0 Fwrap Examples
3.1 Simple fwrap example
3.2 Fwrapper and argument access example
3.3 Interaction between fwrap and trace
4.0 Modifications Made to Trace
5.0 Replacing advice with fwrappers
6.0 Introduction
6.1 Advising a function
6.1.1 Advice of class :before
6.1.2 Advice of class :around
6.1.3 Advice of class :after
6.1.4 More on the position argument
6.2 Removing advice from a function or macro
6.3 Utilities associated with advice
6.4 Examples using advice
6.5 Advising macros

This document describes the new fwrap (Function WRAPper) facility. Fwrappers are intended to replace the advice facility, which is still supported and also documented here. Both facilities allow runtime modification of defined functions without actually redefining the function.

Symbols relating to the fwrap facility and to the advice facility are in the `:excl` package.

## 1.0 The Fwrap Facility

The Fwrap facility is provided, starting in release 6.0, to better implement encapsulation, tracing, and advice functionalities. Highlights of the Fwrap facility include:

- A new function wrapping facility.
- The older, advice encapsulation facility is deprecated. It still exists in autoloadable form and works, but might not remain in future versions.
- The trace macro is re-implemented to use the fwrap facility, instead of the encapsulation facility.
- The macro, def-fwrapper, allows new function wrappers to be created in a lisp-like style, incorporating the use of environments, pre-compilation, and argument identification, in a manner similar to other defining macros such as defun and defmacro. This is in contrast to the forms-based approach of defadvice.
- The advise/advice facility is deprecated. It still exists in autoloadable form and works, but might not remain in future versions. The user is encouraged to switch to the fwrap facility in creating and manipulating advice-like functionality.

## 1.1 Background

The encapsulation facility, as manifested in previous versions of Allegro CL in the advice and trace facilities, has a major drawback, in that the act of encapsulationg a function produces a new, non-eq function object that is installed at the point of the function spec.

This means that for example if #'foo were encapsulated, but had been previously captured and stored in a variable to be later funcalled, the funcalled function does not execute the encapsulating functionality. In practical terms, it means that it is impossible to trace a function object by anything other than its name, and it is thus also impossible to trace a function which cannot be accessed via a function spec (usually a symbol, but possibly a list; see def-function-spec-handler). Also, an internal function such as #'(flet foo bar) which is traced does not retain its trace status when the parent function (i.e. #'foo, in this case) is redefined either by defun, compile, or loading a compiled file.

Another major problem with encapsulations is that it is forms-based, and so acts in the dynamic environment only, while fwrappers operate in the lexical environment. One of the differences is internal, and affects performance rather than coding: to use advice, the argument list must be created so the advice can operate on it. The fwrap facility may not need the arguments list, and if it does not, it will not be created, saving execution time. Further, the arguments are known to advice only from the list: you cannot refer to arguments by name, only by position, and if optional or keywords arguments are defaulted, advice will not know the value (except perhaps by calling the excl:arglist function). Fwrappers can refer to arguments by name because they can be defined with the argument list specified.

Advice also defines the code that does the work and what this code is applied to at once. there is no way to define advice and apply it later to a function. Fwappers can be defined independently of what they might modify (with def-fwrapper) and then apply it to a function (with fwrap) when and only when desired. Fwrapper objects survive dumplisp and can be applied when an image is started.

## 2.0 The Def-fwrapper/fwrap Facility

The term "fwrapper" means "function wrapper". A normal function that implements any functionality is called a primary function for the purposes of this description. An fwrapper, then, is a function that "wraps" the primary function, i.e. it executes at the point of call to the primary function, but its functionality surrounds the call to the actual primary function call, in a similar way that around methods surround the primary method in CLOS. Several fwrappers may wrap a primary function; they are executed in an outer-to-inner order that can be manipulated.

During the time of the execution of fwrapped functions, an attempt is made to preserve the argument list without reproducing it, if possible, in order to reduce consing and maintain speed. The conceptual arglist starts out as a stack pointer and a number-of-arguments variable, and is only converted to a list if any one of the fwrappers in the line forces the argument list to list form. Once an argument-list is made into a list, it remains a list until the primary function is called, when it is re-expanded on the stack.

You create an fwrapper object with the def-fwrapper macro. This macro associates an fwrapper object with a name (a function spec, which is typically a symbol but can be a list), but that name does not name a function or function object and nothing is wrapped with a call to def-fwrapper. You wrap a function independently with a call to fwrap. The function being wrapped is called the primary function.

The fwrapper object can be compiled, just like any operator, with compile.

As we said above, fwrappers wrap around their primary functions and a wrapper must call call-next-fwrapper in order that the primary function (or the next inner wrapper) be called. The return values from the primary function (or the next inner wrapper) are returned from the call to call-next-fwrapper.

Consider the following simple example.

    ;; We define Function bar, which prints a message
    ;; saying bar has been called and what its argument was.
    user(39): (defun bar (x)
                   (format t "Function bar called with argument ~S.~%" x))

    bar
    ;; We define an fwrapper object. Note that bar is not
    ;; specifically mentioned.
    user(40): (def-fwrapper bar-wrap (x)
                   (format t "Fwrapper bar-wrap called with argument ~S.~%" x)
                   (if (integerp x) (call-next-fwrapper))
                   (format t "This form in bar-wrap called!~%"))
    bar-wrap

    ;; bar is not yet wrapped, so it is unaffected:
    user(41): (bar 10)
    Function bar called with argument 10.
    nil

    ;; Now we wrap bar with bar-wrap. barwrapper1 is a symbol
    ;; which serves as the identifier (called the indicator)
    ;; of this wrapper of bar.
    user(42): (fwrap 'bar 'barwrapper1 'bar-wrap)
    #<Interpreted Function bar>

    ;; We now call bar with argument 10. Note that the wrapper
    ;; executes before and after the call to call-next-fwrapper.
    user(43): (bar 10)
    Fwrapper bar-wrap called with argument 10.
    Function bar called with argument 10.
    This form in bar-wrap called!
    nil

    ;; We call bar with a non-integer argument. The conditional
    ;; (integerp x) fails so call-next-fwrapper, and thus bar,
    ;; are not called.
    user(44): (bar t)
    Fwrapper bar-wrap called with argument t.
    This form in bar-wrap called!
    nil

    ;; We unwrap bar:
    user(45): (funwrap 'bar 'barwrapper1)
    #<Interpreted Function bar>

    ;; And now bar behaves as it did when first defined.
    user(46): (bar t)
    Function bar called with argument t.
    nil

    ;; Finally we compile bar and bar-wrap, just to show
    ;; how wrappers are compiled:
    user(47): (compile 'bar)
    bar
    nil
    nil
    user(48): (compile 'bar-wrap)
    bar-wrap
    nil
    nil
    user(49): (fwrap 'bar 'barwrapper1 'bar-wrap)
    #<Function bar>
    user(50): (bar t)
    Fwrapper bar-wrap called with argument t.
    This form in bar-wrap called!
    nil
    user(51):

### Fwrapper notes

- Order of fwrappers. Fwrappers are called in order, from outside to inside. When a new wrapper is added (with fwrap), it is placed outside. The function fwrap-order provides information on the order of fwrappers and can specify the outermost and innermost wrappers. There is no way to specify the order in one action, but repeated calls to fwrap-order can result in any desired order.
- List of fwrappers on a function. fwrap-order provides information on whether a function is fwrapped and what the fwrappers, if any, are.
- Removing fwrappers. Fwrappers are removed with funwrap.
- Modifying an existing fwrapper. Redefining an fwrapper object with def-fwrapper does not modify the behavior of a function already wrapped with the old definition. You must call fwrap again with the same indicator after defining the new fwrapper object.
- Calling fwrap with an already used indicator replaces the wrapper. Fwrappers are associated with indicators, which are typically symbols. If you call fwrap with an already used indicator, the existing fwrapper is replaced rather than a new fwrapper being added.
- Fwrapping macros. When you fwrap a macro, the macroexpansion function is fwrapped. See the example below.

    ;; Here is an example of fwrapping a macro.
    ;;
    ;; Here is the macro definition:
    (defmacro safe-car (x)
       (let ((object (gensym)))
        `(let ((,object ,x))
          (if (consp ,object) (car ,object) ,object))))

    ;; Here is the fwrapper definition:
    (def-fwrapper wrap-safe-car (&rest args)
      (let ((mac (caar args)))
        (format t "The macro ~S has been expanded!~%" mac)
        (format t "The arglist is ~S~%" args))
      (call-next-fwrapper))

    ;;
    user(66): (safe-car '(1 2))
    1
    user(67) (fwrap 'safe-car 'wsc-1 'wrap-safe-car)
    <Interpreted function safe-car @ #x20f1cc21>
    user(68) (safe-car '(1 2))
    The macro safe-car has been expanded!
    The arglist is ((safe-car '(1 2)) nil)
    1
    user(68):

## 2.1 Macroexpansion and Compilation of Fwrappers

def-fwrapper has been designed to work well as a Common Lisp defining form. As a macro, it can be macroexpanded, thus revealing portions of the implementation of the definition it is creating. Because it uses the same techniques as defun and defmacro to create a definition, compilation of a form is efficient in resource-usage, and loading a .fasl file with a compiled def-fwrapper form does not require the compiler to be present, as is the case with compiled defadvice forms.

To find out what a def-fwrapper form is doing, it is recommended to use a standard macroexpansion technique:

    (pprint (macroexpand '(def-fwrapper foo-wrap ...)))

which will reveal the implementation of foo-wrap.

To use an fwrapper that is compiled, without requiring the compiler in the image, simply place the def-fwrapper form into a source file, compile the file in a lisp with a compiler, and load the resulting fasl file into a lisp (not necessarily containing a compiler). The fwrapper will then be defined as a compiled function, and fwrap may then be used to fwrap the primary function.

## 3.0 Fwrap Examples

## 3.1 Simple fwrap example

We start with a simple example where a simple fwrapper is defined for wrapping a definition of a factorial function. Note that before the fwrap call, the fwrapper has no effect on the execution of fact.

    user(1): (defun fact (n)
    	  (if (= n 0)
    	      1
    	    (* n (fact (1- n)))))
    fact
    user(2): (def-fwrapper factx (n)
                (when (= (mod n 2) 1)
                  (format t "in trace, n= ~d~%" n))
                 (call-next-fwrapper))
    factx
    user(3): (fact 10)
    3628800
    user(4): (fwrap 'fact :my-trace 'factx)
    #<Interpreted Function fact>
    user(5): (fact 10)
    in trace, n= 9
    in trace, n= 7
    in trace, n= 5
    in trace, n= 3
    in trace, n= 1
    3628800
    user(6):

## 3.2 Fwrapper and argument access example

There have been many requests for arglist access in trace. def-fwrapper allows the lambda list to match that of the primary function, in which case arguments can be accessed by name.

    (defun foo (x y &optional z &rest args) ...)
    (def-fwrapper foo-wrap (x y &optional z &rest args) ...)

or, if it is desired to work with the arguments as a list, they can be specified as such:

    (def-fwrapper foo-wrap (&rest arglist) ...)

## 3.3 Interaction between fwrap and trace

The following example shows how trace and fwrap can work together and how fwrap-order can be manipulated to govern the interaction. It also demonstrates the use of describe on fwrapped functions. Of course, this example is not a good example of modular design, but its intent is to show how such interdependence behaves.

    user(1): (defvar *fact-arg* 0)
    *fact-arg*
    user(2): (defun fact (num)
           (if (equal num 0) 1
    	   (* num (fact (- num 1)))))
    fact
    user(3): (def-fwrapper factwrap (num)
               (setq *fact-arg* num)
               (call-next-fwrapper))
    factwrap
    user(4): (fwrap 'fact :my-advice 'factwrap)
    #<Interpreted Function fact>
    user(5): :trace (fact  :condition (= *fact-arg* 3))
    (fact)
    user(6): (fact 12)
     0: (fact 2)
     0: returned 2
    479001600
    user(7): (describe #'fact)
    #<Interpreted Function fact> is a function.
      The arguments are (num)
      It has the following indicator/fwrapper pairs, from outer to inner:
    :trace            #<Closure trace-fwrapper @ #x30c601ea>
    :my-advice        #<Interpreted Function factwrap>
    user(8): (fwrap-order #'fact :outer :my-advice)
    (:my-advice #<Interpreted Function factwrap> :trace
     #<Closure trace-fwrapper @ #x30c601ea>)
    user(9): (describe #'fact)
    #<Interpreted Function fact> is a function.
      The arguments are (num)
      It has the following indicator/fwrapper pairs, from outer to inner:
    :my-advice        #<Interpreted Function factwrap>
    :trace            #<Closure trace-fwrapper @ #x30c601ea>
    user(10): (fact 12)
     0: (fact 3)
     0: returned 6
    479001600
    user(11):

## 4.0 Modifications Made to Trace

The only change that has been made to the trace interface is the addition of the :not-inside trace option. It is similar to the :inside option, in that it checks whether or not the specified function is active when the traced function is entered. However, it requires that the specified function not be on the stack. Both :inside and :not-inside work together and check simultaneously, for maximum efficiency.

Other changes that are not functional are mostly bug fixes and enhancements to the operation of trace:

- The :inside (and :not-inside) options operate on the traced function's thread to find functions, rather than to setup statefulness to determine that a function has been entered. This fixes a bug where if (trace ((foo :inside bar))) is specified, and bar is entered on one thread and foo is then entered on another, foo was being traced.
- Under some circumstances after a :break-after point was hit, no tracing could work anymore until that break loop was returned from.
- Redefining functions that had traced internal functions (flets/labels) tended to forget about the fact that they had been traced.
- Generic-functions lost their genericity when traced. This caused things like compute-applicable-methods and others to fail on traced gfs.

## 5.0 Replacing advice with fwrappers

We recommend that you replace any advice code you might have with fwrapper code (though advice is still supported). This section indicates how such code might be rewritten.

Things to note:

- You must insert a (call-next-fwrapper) if you want the wrapped function to run. With before and after advice, the advised function ran automatically, after the before advice and before the after advice. Only around advice needed an indication of where the advised function was to be run. Fwrappers are like around advice in this respect: the function (and also inner fwrappers) will only run when call-next-fwrapper is called (it takes no arguments).
- Fwrappers must be defined and applied. Advice is defined and applied to the advised function in one operation. With fwrappers, the definition and the application are separate actions. Define an fwrapper with def-fwrapper and applied with fwrap.
- Positioning is more complicated. Advice could be positioned using an argument to advise. Fwrappers are always positioned by fwrap in the outermost position. If you wish to rearrange fwrappers, you must use fwrap-order.
- Each fwrapper must be removed individually. The unadvise macro could remove many pieces of advice (called with no arguments, it removed all advise from everything). funwrap removes only the single fwrapper specified by the indicator.

Here is an example from the advice examples section below with the corresponding fwrapper code.

    ;; First we define a function to be advised.
    (defun perm-and-comb (n k)
      (let ((perm (/ (fact n) (fact (- n k)))))
        (values perm (/ perm (fact k)))))

    (defun fact (n)
      (if (= n 1) 1 (* n (fact (1- n)))))

    ;; This before advice:
    (excl:advise perm-and-comb :before order nil
      (let ((n (car excl:arglist)) (k (cadr excl:arglist)))
        (if (>= k n) (setq excl:arglist (list k n)))))

    ;; would be replaced with this fwrapper (note the arguments
    ;; are specified and can be refered to by name):
    (def-fwrapper order-wrap (n k)
      (if (>= k n)
        (let ((hold n))
          (setq n k)
          (setq k hold)))
      (call-next-fwrapper))

    ;; and applied with a call to fwrap:
    (fwrap 'perm-and-comb 'w1 'order-wrap)

    ;; This around advice:
    (excl:defadvice perm-and-comb (error :around)
      (let ((n (car excl:arglist)) (k (cadr excl:arglist)))
        (if (or (not (integerp n)) (not (integerp k))
                (>= k n) (> 1 k))
            (error "Improper arguments to comb-and-perm: ~D ~D" n k)
            :do-it)))

    ;; would be replaced with this fwrapper:
    (def-fwrapper arg-check-wrap (n k)
      (if (or (not (integerp n)) (not (integerp k))
                (>= k n) (> 1 k))
            (error "Improper arguments to comb-and-perm: ~D ~D" n k))
      (call-next-fwrapper))

    ;; and applied with
    (fwrap 'perm-and-comb 'w2 'arg-check-wrap)

    ;; but we are not done. arg-check-wrap is now outside
    ;; order-wrap, but it should be inside (around advice was
    ;; automatically inside before advice) so we have to
    ;; call fwrap-order (the value of the :inner argument is
    ;; placed innermost):
    (fwrap-order 'perm-and-comb :inner 'w2)

## 6.0 Introduction

The advice facility has been replaced with the fwrapper facility, described above in the document. Advice is still supported, and the remainder of this document is the (somewhat abreviated) description of advice and advising functions from the release 5.0.1 and earlier documentation.

The advice facility allows you to affect the action of a function by adding code before, around or after the evaluation of forms defining a function. You may do this with interpreted or compiled functions without changing the source code or having to recompile. This facility is particularly useful in cases where you have compiled code but do not have access to the source code (for example, in a commercial application).

Advice works on macros as well as functions but what gets advised is the macro-expander function, not the forms returned by the macro expansion. Thus, advising a macro is more complex than advising a function and there are fewer situations where advising a macro is helpful. Section 6.5 Advising macros below describes how to advise a macro. The remainder of this document describes advising functions only.

Note that you cannot advise special forms, such as setq or progn, and that some standard Common Lisp functions that are normally compiled in-line, for example car, will not run advice code when compiled. (Since many Common Lisp functions may be compiled in-line, care should be used when advising such functions, since advice will not be run if a function is compiled inline. Compiling: Help with declarations describes how to get information during the code generation phase of compilation.)

To get the nomenclature straight:

- advice is attached to a function;
- the act of attaching advice to functions is advising them;
- the code that implements the advice is advice code.
- This document describes the advice facility.
- You use either the advice facility or advice when advising functions.

The functions for the advice facility are in the `trace` module, which may not be included with the default Lisp image file. If you wish to use advice, the module must be loaded. It will be autoloaded with the first call to most advice functions, but you can load it explicitly, for example by evaluating the following form:

    (require :trace)

If you commonly use advice, you may want to put such a form in your .clinit.cl file.

Advising a function has a number of uses:

1. To put debugging information in a function for testing (checking arguments and return values, for example). Doing so is useful particularly since advice code can be very easily removed (with a call to unadvise). Note that advice code is evaluated each time a function is called, so you may wish to avoid doing output in advice code in a recursive function.
2. To add extra argument checking or conditioning to an already written function without modifying the function itself.
3. To modify code provided by others where you do not have access to the source (and therefore cannot modify the code directly).

If a function which has been advised (that is: advice code is attached to it) is redefined with defun, either at the top level or by loading a new definition from a file, the function will still have the same advice. The advice will be removed only with a call to unadvise or a related function.

Advice code can access the list of arguments passed to the advised function, which is bound to the variable excl:arglist, and the list of returned values, which is bound to the Lisp variable values. The advice code may look at and change either the argument list or the list of returned values. (Which values can be modified depends on the type of advice. :before and :around advice may change the argument list, but the list bound to values is not formed until all :around advice has been evaluated. :after advice has access to values, but, since the advised function has already run, the argument list is of little interest to :after advice.) If you change either list, be careful to ensure that you preserve such structure as is necessary. For example, if you replace the argument list, you must be sure the new list has as least as many elements as there are required arguments to the advised function.

There are five functions for advising and unadvising:

advise

advise-1

unadvise

unadvise-1

defadvice

The following functions and global variable are also useful:

advised-functions

compile-advice

describe-advice

`*compile-advice*`

advise and unadvise are macros, advise-1 and unadvise-1 are the functions which implement the macros. The macro defadvice allows users to use a defun-like syntax for advice.

## 6.1 Advising a function

You may advise functions with advise, advise-1 or defadvice. advise and advise-1 take as arguments the name of the function being advised, the class, described next, the position (relative to other advice, see below), the name given to the piece of advice being added, and the forms to run when the function is called. Advice comes in three types, called the class of advice. These are :before, :around, and :after. Let us look more closely at the classes of advice and at the possible values for the position argument.

## 6.1.1 Advice of class :before

:before advice will be evaluated before the function call. The argument list with which the function was called is available to :before advice. The argument list is stored as the value of the variable excl:arglist. You may check the values in this list, change the values or replace the list altogether with a new list. (If you replace the list, be careful that it have the correct format for the function (number and types of arguments in the list) or you may get an error, or worse, a wrong result but no error.) :before advice is used only for its side effects. Any value returned by :before advice code is ignored.

## 6.1.2 Advice of class :around

:around advice places the function call inside the code of the advice. The keyword `:do-it` signals where the function should be called within the advice code. When Lisp encounters the `:do-it`, it calls the next piece of :around advice, if there is more, or the function. When the function returns, the :around advice code continues execution. `:do-it` may appear several times in `:around` advice. Normally, it is placed in conditional code, e.g.

    (if (zerop (car excl:arglist))(+ 5 :do-it)(* 7 :do-it))

In that case, the system will encounter one or the other `:do-it`, but not both. However, it is allowed to have several `:do-it`'s, all of which are evaluated. In that case, the succeeding :around advice and the advised function are evaluated more than once. :after advice is still evaluated only once, however. :around advice can work with `excl:arglist` before the `:do-it`. Since the advised function is run at the location specified by the `:do-it`, the values the function return are available to `:around` advice just like with any function call. The list bound to the variable values is not set up until after all `:around` advice is run, then values is bound to the list of values returned by the final piece of :around advice. Note that if the function returns multiple values, these should be caught with multiple-value-bind or multiple-value-list or some similar function if you are interested in the values beyond the first.

## 6.1.3 Advice of class :after

`:after` advice is evaluated after the function has completed, but before control is passed back to whatever called the function. `:after` advice may examine and change the list of returned values from the last piece of `:around` advice (or the function, if there is no :around advice) stored in the variable values. `:after` advice is used only for its side effects. Any value returned by the `:after` advice code is ignored. The list bound to values is returned from the now completed function call.

## 6.1.4 More on the position argument

Let us look at the position argument more closely. As we said above, advice is numbered 0, 1, 2, ..., n-1, if there are n pieces of advice of a given class. Advice with a smaller index is run farther from the function call than advice with a larger index. Regardless of what position was specified for advice when it was defined, its real position is the number in the list from 0 to n-1. A new piece of advice of the same class will be placed according to its position argument. If that argument is a number, that number is placed with respect to the list 0, ..., n-1. The argument can also be a name of other advice, in which case it is placed immediately farther from the function call than that named advice. Or the argument can be `nil`, in which case the advice is placed farthest from the function call. Here are some examples. Let us assume that the function foo has no :before advice on it when we start. We will define advice, and show after each definition the order in which the advice will be run.

    (defun foo (&rest args) nil)

    (excl:advise foo :before
      john 10 (format t "hello john"))

Things will be run in the following order:

    john foo

    (excl:advise foo :before
      cathy 5 (format t "hello cathy"))

Things will be run in the following order:

    john cathy foo

## 6.2 Removing advice from a function or macro

The unadvise macro and the unadvise-1 function are used to remove advice from functions or macros. Note that unadvise called without arguments removes all advice from all functions and macros.

Let us look at some examples of unadvising functions. Note particularly how the value `nil` for the arguments to unadvise is interpreted.

    (excl:unadvise func :before joe)

removes the `:before` advice named joe from func.

    (excl:unadvise func :before nil)

removes all `:before` advice from func.

    (excl:unadvise func nil joe)

removes all advice named joe (whether `:before`, `:after` or `:around`) from func.

    (excl:unadvise nil :around)

removes all `:around` advice from all functions.

    (excl:unadvise func)

removes all advice from func.

    (excl:unadvise nil)

or

    (excl:unadvise)

removes all advice from all functions.

## 6.3 Utilities associated with advice

The following functions and variables are useful when using the advice facility.

advised-functions, a function, returns a list of all advice on all advised functions.

`*compile-advice*`, a variable, if true, causes newly added advice to be compiled automatically. If `nil`, newly added advice is left uncompiled (advice can be compiled with compile-advice).

compile-advice, a function, compiles the advice on the function named by the argument.

describe-advice, a function, prints a description of advice on the function named by the required argument to the stream named by the optional argument (which defaults to `*standard-output*`).

## 6.4 Examples using advice

In the following example, we define a function and then add advice code which checks and (if necessary) modifies the arguments, signals an error for wrong arguments, and takes care of special cases not actually handled by the function itself. We do not suggest that the following example represents good programming style or that using advice is preferable to actually rewriting the original function. The example is designed only to show how advice works. We apply advice using excl:advise, excl:advise-1, and excl:defadvice.

Consider the following function, which calculates both the number of permutations of n things taken k at a time and the number of combinations of n things taken k at a time, returning both as multiple values.

    (defun perm-and-comb (n k)
      (let ((perm (/ (fact n) (fact (- n k)))))
        (values perm (/ perm (fact k)))))

    (defun fact (n)
      (if (= n 1) 1 (* n (fact (1- n)))))

There are several things to notice about perm-and-comb. One is that if you put the arguments in the wrong order (i.e. if the first argument is less than or equal to the second) or if either argument is not an integer, the function goes into an infinite loop. The first pieces of :before advice check that the arguments are in the right order.

    (excl:advise perm-and-comb :before order nil
      (let ((n (car excl:arglist)) (k (cadr excl:arglist)))
        (if (>= k n) (setq excl:arglist (list k n)))))

The next piece of advice checks that the arguments are integers, truncating them if they are not.

    (excl:advise perm-and-comb :before truncate nil
      (let ((n (car excl:arglist)) (k (cadr excl:arglist)))
        (if (not (integerp n))
            (setf (car excl:arglist) (truncate n)))
        (if (not (integerp k))
            (setf (cadr excl:arglist) (truncate k)))))

(We are not suggesting that it is good programming style to change arguments without warning or notice. Generally, that is a bad idea.)

All the cases where numeric arguments to perm-and-comb cause an infinite loop have not yet been covered. The following :around advice completely checks the arguments (assuming that they are numbers), causing a fatal error if the arguments will cause an infinite loop. Some of the checks are redundant, of course, given the :before advice already applied to perm-and-comb.

    (excl:defadvice perm-and-comb (error :around)
      (let ((n (car excl:arglist)) (k (cadr excl:arglist)))
        (if (or (not (integerp n)) (not (integerp k))
                (>= k n) (> 1 k))
            (error "Improper arguments to comb-and-perm: ~D ~D" n k)
            :do-it)))

Now one last change. The form (perm-and-comb 10 0) will generate an error (and, indeed will cause an infinite loop, since (fact 0) will not stop), but the number of permutations and the number of combinations of 10 things taken none at a time is in fact defined. (There is only one way: choose nothing.) Let us add one final piece of :around advice, which will return 1 and 1 when the argument k is 0. This advice must run before the error advice defined above, since the argument k being 0 will cause a fatal error if the error advice sees it. By specifying a position of error, we guarantee that this new advice is placed farther from the actual function call than the error advice. Note too that the forms argument is contained in a list.

    (excl:advise-1 'perm-and-comb :around 'zerotest 'error
      '((let ((k (cadr excl:arglist)))
          (if (zerop k) (return (values 1 1)) :do-it))))

Notice the use of return. Since we know the answer, there is no reason to call the function.

For the final example, let us suppose that you are actually interested in the number of permutations and the ratio of permutations to combinations. Here is :after advice that will change the returned values to be what you want. The first returned value is left alone and the second is replaced by the ratio of the first and the original second.

    (excl:advise perm-and-comb :after nil nil
      (let ((p (car values)) (c (cadr values)))
        (setq values (list p (/ p c)))))

Note that if the :around advice named zerotest returns without calling perm-and-comb, this `:after` advice will not be run. In this case, that is okay, since the values returned by zerotest would not be changed by the `:after` advice. However, it is important to keep in mind all of the advice on a particular function to ensure that different pieces are not working at cross purposes. The function describe-advice, defined above, is useful for that purpose. When all of the advice given above has been attached to perm-and-comb, describe-advice prints the following.

    USER(21): (let ((*print-pretty* t))
               (excl:describe-advice 'perm-and-comb))
    Before advice:
    (truncate
      (let ((n (car arglist))
            (k (cadr arglist)))
        (if (not (integerp n)) (setf (car arglist) (truncate n)))
        (if (not (integerp k)) (setf (cadr arglist) (truncate k)))))
    (order
      (let ((n (car arglist))
            (k (cadr arglist)))
        (if (>= k n) (setq arglist (list n k)))))
    Around advice:
    (zerotest
      (let ((k (cadr arglist)))
        (if (= k 0) (return (values 1 1)) :do-it)))
    (error
      (let ((n (car arglist))
            (k (cadr arglist)))
        (if (or (not (integerp n)) (not (integerp k))
                (>= k n) (> 1 k))
            (error "Improper arguments to comb-and-perm: ~D ~D" n k)
            :do-it)))
    After advice:
    (nil
      (let ((p (car values)
          (c (cadr values)))
        (setq values (list p (/ p c)))))

## 6.5 Advising macros

You may advise macros as well as functions but note that both what is being advised and where the advice is run is very different when a macro is advised than when a function is advised. When you advise a macro, you advise the macro expansion function. This function takes two arguments, the whole form and the environment. The macro name will be the car of the form argument. The advice will thus be run at macro expansion time. Macros are typically advised for purposes of source-file recording, tracing, and other informational purposes. If you advise macros in order to affect their behavior (in ways other than simply printing or storing information) you must be very careful not to break or confuse the macro expansion system.

The effect and behavior of advice applied to macros (and placed on the macro expansion function) is the same as that described above for functions. Therefore, name, position, and class are all interpreted the same way and the advice is removed with unadvise (or unadvise-1) just as with functions.

Here is a simple example. We define the macro safe-car. Then we advise it to print a message whenever it is expanded.

    USER(25): (defmacro safe-car (x)
                (let ((object (gensym)))
                   `(let ((,object ,x))
                      (if (consp ,object) (car ,object) ,object))))
    safe-car
    USER(26): (safe-car '(1 2))
    1
    USER(27): (advise safe-car :before expand-notify nil
                  (let ((mac (caar excl:arglist)))
                    (format t "The macro ~S has been expanded!~%" mac)))
    safe-car

    ;; When we call SAFE-CAR, the macro is expanded.

    USER(28): (safe-car '(1 2))
    The macro safe-car has been expanded!

    1
    USER(29):

Copyright (c) 1998-2022, Franz Inc. Lafayette, CA., USA. All rights reserved.
This page was not revised from the 10.0 page.
Created 2019.8.20.

ToC | DocOverview | CGDoc | RelNotes | FAQ | Index | PermutedIndex
| Allegro CL version 10.1 Unrevised from 10.0 to 10.1. 10.0 version