method hooks
0.1.0simple qualifiable hooks defined like methods with the option to modify the dispatch method and how dispatch happens
WARNING!
I do not consider this library to be a good solution to the problem it solves, the code quality is poor and it should be re-written, but it's good enough.
Method Hooks
method-hooks provides simple hooks dispatched by methods and supports method combination and qualifiers.
documentation can be viewed here
Motivation
A friend thought that methods qualified with progn
with the same type specializer lists would accumulate to run like hooks,
Which could be quite useful so here we are.
Features
- create hooks with
defhook
- set the default qualifier to use for hooks in a generic with
define-hook-generic
. - change how hooks dispatch with
finalize-dispatch-method
or add behaviour to the dispatch method, which will enable the use ofcall-next-method
. - create a dispatcher for a new method-combination type with
define-dispatch
, or to change the behaviour of hook dispatch for an existing qualifier. set-dispatch-for-qualifier
to set the default dispatcher to use for a given qualifierdispatch
to dispatch hooks for a specific method, (useful withinfinalize-dispatch-method
).- suppression of no-applicable-method error to get extensible hook points by passing (:hook-point t) to define-hook-generic.
Usage
Getting started
You can jump straight into defining hooks, they will by default (where define-hook-generic hasn't been used for the generic you're using) be unqualified just like normal methods.
(ql:quickload :method-hooks)
(defgeneric foo (a))
(method-hooks:defhook foo user ((a string))
(print a))
(method-hooks:defhook foo hey :before ((a string))
(print "hey"))
> (foo "me")
"hey"
"me"
Using define-hook-generic with defhook
If you want to defhook
to remember what qualifier to use for a generic, you can use define-hook-generic
which takes all the same options as defgeneric
and optionally takes :default-qualifier
which by default will be the method-combination type supplied. If no method combination type has been supplied then by default define-hook-generic
will use progn
as the default qualifier & combination.
(define-hook-generic baz ()) ; defaults to progn for least astonishment
(defhook baz meow () (print "meow"))
(defhook baz woof () (print "woof"))
> (baz)
"woof"
"meow"
(WOOF MEOW)
Here is an example where we will use the +
combination-type to show that defhook
by default will use the combination-type supplied as the default qualifier.
(define-hook-generic adding (x) ; remembers the method combination type and uses that as default.
(:method-combination +)) ; can be overridden with (:default-qualifier :unqualified) (or another combination type)
(defhook adding once ((x integer)) x)
(defhook adding twice ((x integer)) x)
> (adding 3)
6
The keyword :unqualified
can be supplied as a qualifier to defhook
, define-hook-generic
, set-dispatch-for-qualifier
and anything else exported by this system as this is how unqualified methods are distinguished internally, however this will likely never be unnecessary.
defining a new dispatcher
There are some dispatchers already defined in /src/known-dispatchers.lisp and these are good examples to go by.
Using finalize-dispatch-method
defhook
expands with a definition for the dispatch method, this is so that each dispatch-method doesn't have to be finalized.
If you wanted to edit the dispatch method you can and you can do anything you want in there, you must however dispatch the hooks yourself with dispatch
or by hand.
(finalize-dispatch-method adding ((x integer))
(format t "dispatching hooks")
(dispatch adding + ((x integer))))
dispatching without a dispatcher
To dispatch by hand you would as of writing have to understand internals. The definition for dispatch
shows you how to do this, I think I will make this easier if there is demand in future.
what are specialized-lambda-list, vanilla-lambda-list, type-list, descriptive-lambda-list?
using method-hooks::destructure-specialized-lambda list will give you a good idea e.g.
> (destructure-specialized-lambda-list descriptive-lambda-list vanilla-lambda-list type-list '((x integer) y)
(values descriptive-lambda-list type-list vanilla-lambda-list))
((X INTEGER) (Y T))
(INTEGER T)
(X Y)
It's also important to note that hooks are interned by the gf-name and type-list not the specialized-lambda-list as the variable symbols in the specialized-lambda-list can change.
More (and more practical) examples
Here is a version of cl-matrix where event listening is done with method-hooks.
Here is a version where I badly abuse deeds for comparison.
System Information
Definition Index
-
METHOD-HOOKS
No documentation provided.-
EXTERNAL CLASS DISPATCHER
No documentation provided. -
EXTERNAL CLASS HOOK
No documentation provided. -
EXTERNAL FUNCTION CLEAR-HOOK-TABLE
will not require recompilation of all the forms unless an existing hook definition is redefined or a method is redefined
-
EXTERNAL FUNCTION DISPATCH-FOR-QUALIFIER
- QUALIFIER
the dispatcher used for the given qualifier See define-dispatch See dispatcher
-
EXTERNAL FUNCTION SET-DISPATCH-FOR-QUALIFIER
- QUALIFIER
- DISPATCH
accepts two symbols, sets the dispatcher to be used for the given qualifier See define-dispatch See dispatcher
-
EXTERNAL FUNCTION SPECIFIC-HOOKS-FOR-GENERIC
- TYPE-LIST
- GENERIC-FUNCTION
- QUALIFIER
get the hooks specific to the type specializer list and qualifier
-
EXTERNAL GENERIC-FUNCTION DISPATCH-FUNCTION
- OBJECT
No documentation provided. -
EXTERNAL GENERIC-FUNCTION (SETF DISPATCH-FUNCTION)
- NEW-VALUE
- OBJECT
No documentation provided. -
EXTERNAL GENERIC-FUNCTION DISPATCH-FUNCTION-CONSTRUCTOR
- OBJECT
No documentation provided. -
EXTERNAL GENERIC-FUNCTION (SETF DISPATCH-FUNCTION-CONSTRUCTOR)
- NEW-VALUE
- OBJECT
No documentation provided. -
EXTERNAL GENERIC-FUNCTION HOOK-NAME
- OBJECT
No documentation provided. -
EXTERNAL GENERIC-FUNCTION (SETF HOOK-NAME)
- NEW-VALUE
- OBJECT
No documentation provided. -
EXTERNAL GENERIC-FUNCTION QUALIFIER
- OBJECT
No documentation provided. -
EXTERNAL GENERIC-FUNCTION (SETF QUALIFIER)
- NEW-VALUE
- OBJECT
No documentation provided. -
EXTERNAL MACRO DEFHOOK
- GENERIC-FUNCTION
- HOOK-NAME
- &REST
- ARGS
define a hook to be to be called by the effective method. This macro has roughly the same signature as defmethod `(DEFHOOK GENERIC-FUNCTION HOOK-NAME {QUALIFIER} SPECIALIZED-LAMBDA-LIST &BODY BODY)` creates a function `hook-name` with the `body` then creates a method to dispatch all hooks matching the type-list for the given generic-function. See define-hook-generic See finalize-dispatch-method
-
EXTERNAL MACRO DEFINE-DISPATCH
- NAME
- LAMBDA-LIST
- &BODY
- BODY
the lambda list should accept two arguments: the list of arguments given by the current method call. the specific hooks (as named or unamed functions) for the qualified method (that we will be dispatching from). you should then use the arguments to dispatch the specific hooks as you wish in the body. if the body returns a result, by default the method will also return that result, this can be overriden with finalize-dispatch-method. See finalize-dispatch-method See dispatch
-
EXTERNAL MACRO DEFINE-HOOK-GENERIC
- NAME
- GF-LAMBDA-LIST
- &REST
- OPTIONS
utility to help with gf's with method combination by remembering the combination type by default the combination type becomes the default qualifier for any newly defined hooks this can be overriden by not using this and using defgeneric or supplying the option :default-qualifier. supplying `:hook-point t` will create a method qualified with the default-qualifier so that the generic acts like an extensible hook point and will not signal no-applicable-method error when no hooks have been defined. See defhook
-
EXTERNAL MACRO DISPATCH
- GENERIC-FUNCTION
- QUALIFIER
- SPECIALIZED-LAMBDA-LIST
dispatch the hooks using the default dispatcher for the given qualified specific method. See define-dispatch See dispatch-function See set-dispatch-for-qualifier
-
EXTERNAL MACRO FINALIZE-DISPATCH-METHOD
- GENERIC-FUNCTION
- &REST
- ARGS
add a body to the method which dispatched the hooks for the given specialized-lambda-list useful if you wanted to use call-next-method For the definition to be effective, it must be defined after every specific hook for the same method hence finalize. See defhook
-
EXTERNAL MACRO MAKE-DISPATCHER
- FUNCTION-CONSTRUCTOR
No documentation provided.
-