cl-advice

2023-02-15

Portable advice for Common Lisp

Upstream URL

github.com/szos/cl-advice

Author

szos at posteo dot net

License

LGPL
README
CL-ADVICE

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 functions make-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 function make-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 conversion

2.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 macro defun-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-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 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-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

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 body

Functions 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 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

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-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-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.

3.11Function REPLACE-ADVICE

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 isntfound. Must be one of :prepend, :append, or NIL.

3.12Function LIST-ADVICE

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.

3.13Function REMOVE-ADVICE

remove-advice /type fn advice/ &key test

Remove 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

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

Dependencies (2)

  • closer-mop
  • fiveam

Dependents (0)

    • GitHub
    • Quicklisp