[Zope-PTK] Hooks

Dan L. Pierson dan@sol.control.com
Mon, 21 Aug 2000 10:22:49 -0400 (EDT)


Shane Hathaway writes:
 > Fabio Forno wrote:
 > > 
 > > Perhaps it's a crazy idea, but what about a fully configurable hook map,
 > > so that we allow the users to add the hook they want?
 > 
 > An interesting idea.  The one concern I have is that users really won't
 > care about writing hooks for most things.  For any sufficiently sizable
 > change, users will want to write subclasses, not hook implementations. 
 > If you can develop an argument against that, I see no reason why we
 > shouldn't write a generalized "hookable tool generator" that does all
 > the work for us.

Seems like you're starting to reinvent a Lisp advice system.  This is
an interface for wrapping a function or method in a new function such
that all calls will automagically go through the wrapper.
The docs for a simple version that's part of GNU Emacs are at the end
of this message.  A Python interface might look like:

def advise(func, context, where, body, doc = None):
    """ Wrap function 'func' so that 'body' is executed in the
relationship to func specified by 'where'.  'context' is the class or
module that func is part of (i.e. context.__dict__ will be modified to
accomplish this wrapping).  'where' can be one of: ADVISE_BEFORE,
ADVISE_AFTER, ADVISE_AROUND.  The advised function will be available
as 'self.__advised__' and the next advice wrapper to be called will be
available as 'self.__advise_next__' (if there is no inner advice
wrapper, self.__advised__ and self.__advise_next__ will be the same).
'body' is a string that will be used to construct the real definition
of func.  'doc' is an optional doc string for func. """

    # Implementation left as an exercise for the reader :-)

I think that the main pain in implementing this is likely to be that
body has to be a string because there's nothing like Lisp macros in
Python.  On the other hand, this shouldn't be too hard with triple
quoted strings.

Here's the bit of Emacs Lisp doc I threatened you with earlier:

`defadvice' is a compiled Lisp macro
  -- loaded from "advice"

Documentation:
Defines a piece of advice for FUNCTION (a symbol).
The syntax of `defadvice' is as follows:

  (defadvice FUNCTION (CLASS NAME [POSITION] [ARGLIST] FLAG...)
    [DOCSTRING] [INTERACTIVE-FORM]
    BODY... )

FUNCTION ::= Name of the function to be advised.
CLASS ::= `before' | `around' | `after' | `activation' | `deactivation'.
NAME ::= Non-nil symbol that names this piece of advice.
POSITION ::= `first' | `last' | NUMBER. Optional, defaults to `first',
    see also `ad-add-advice'.
ARGLIST ::= An optional argument list to be used for the advised function
    instead of the argument list of the original.  The first one found in
    before/around/after-advices will be used.
FLAG ::= `protect'|`disable'|`activate'|`compile'|`preactivate'|`freeze'.
    All flags can be specified with unambiguous initial substrings.
DOCSTRING ::= Optional documentation for this piece of advice.
INTERACTIVE-FORM ::= Optional interactive form to be used for the advised
    function.  The first one found in before/around/after-advices will be used.
BODY ::= Any s-expression.

Semantics of the various flags:
`protect': The piece of advice will be protected against non-local exits in
any code that precedes it.  If any around-advice of a function is protected
then automatically all around-advices will be protected (the complete onion).

`activate': All advice of FUNCTION will be activated immediately if
FUNCTION has been properly defined prior to this application of `defadvice'.

`compile': In conjunction with `activate' specifies that the resulting
advised function should be compiled.

`disable': The defined advice will be disabled, hence, it will not be used 
during activation until somebody enables it.

`preactivate': Preactivates the advised FUNCTION at macro-expansion/compile
time.  This generates a compiled advised definition according to the current
advice state that will be used during activation if appropriate.  Only use
this if the `defadvice' gets actually compiled.

`freeze': Expands the `defadvice' into a redefining `defun/defmacro' according
to this particular single advice.  No other advice information will be saved.
Frozen advices cannot be undone, they behave like a hard redefinition of
the advised function.  `freeze' implies `activate' and `preactivate'.  The
documentation of the advised function can be dumped onto the `DOC' file
during preloading.

Look at the file `advice.el' for comprehensive documentation.