architecture.hooks

2018-12-10

No Description

Upstream URL

github.com/scymtym/architecture.hooks

Author

Jan Moringen <jmoringe@techfak.uni-bielefeld.de>

Maintainer

Jan Moringen <jmoringe@techfak.uni-bielefeld.de>

License

LLGPLv3
README
cl-hooks README

1Prelude :noexport:

  (asdf:load-system :cl-hooks)

2Introduction

A hook, in the present context, is a certain kind of extension pointin a program that allows interleaving the execution of arbitrarycode with the execution of a the program without introducing anycoupling between the two. Hooks are used extensively in theextensible editor Emacs.

In the Common LISP Object System (CLOS), a similar kind of extensibility is possible using the flexible multi-method dispatch mechanism. It may even seem that the concept of hooks does not provide any benefits over the possibilites of CLOS. However, there are some differences:

  • There can be only one method for each combination of specializersand qualifiers. As a result this kind of extension point cannot beused by multiple extensions independently.
  • Removing code previously attached via a :before, :after or :aroundmethod can be cumbersome.
  • There could be other or even multiple extension pointsbesides :before and :after in a single method.
  • Attaching codes to individual objects using eql specializers canbe cumbersome.
  • Introspection of code attached a particular extension point iscumbersome since this requires enumerating and inspecting themethods of a generic function.
This library tries to complement some of these weaknesses ofmethod-based extension-points via the concept of hooks.https://travis-ci.org/scymtym/architecture.hooks.svg

3Hooks

3.1Definition

A hook is an extension point consisting of
  • A name (a symbol or some form)
  • A list of handlers
  • ftype?
  • A result combination
  • A documentation string

There are several kinds of hooks defined in this library, but new kinds of hooks can easily be defined by adding methods to the generic functions hook-handlers, (setf hook-handlers), hook-combination, (setf hook-combination), documentation and (setf documentation).

The following sections briefly discuss the three kinds of hooks that are currently defined in the library.

Mention documentation

3.2Variable Hooks

The most straightforward approach to implementing a hook is touse a variable. The variable is used as followed
Symbol Name
name of the hook
Symbol Value
list of handlers currently attached to the hook
Symbol Documentation
if no dedicated hook documentation isinstalled using (setf (hook-documentation ...) ...), thedocumentation of the symbol as a variable is used

Consider the following example

  (defvar *my-hook* nil
    "My hook is only run for educational purposes.")

  (hooks:add-to-hook '*my-hook*
                     (lambda (x)
                       (format t "my-hook called with argument ~S~%" x)))

  (hooks:run-hook '*my-hook* 1)
  (documentation '*my-hook* 'hooks::hook)

3.3Internal Object Hooks

Hooks can also live in other places like object slots:
  (defclass my-class ()
    ((my-hook :initarg  :my-hook
              :type     list
              :initform nil
              :documentation
              "This hook bla bla")))

  (defvar *my-object* (make-instance 'my-class))

  (hooks:object-hook *my-object* 'my-hook)

Operation on an intern object hook work in the usual way:

  (hooks:add-to-hook (hooks:object-hook *my-object* 'my-hook)
                     (lambda (x)
                       (format t "my-hook called with argument ~S~%" x)))

  (hooks:object-hook *my-object* 'my-hook)
  (format t "bla~%")
  (hooks:run-hook (hooks:object-hook *my-object* 'my-hook) 1)
For object internal hooks, the documentation of the backing slot isused as the hook's documentation:
  (documentation (hooks:object-hook *my-object* 'my-hook) 'hooks::hook)

3.4External Object Hooks

Or outside of objects:
  (defparameter *external-hook* (hooks:external-hook *my-object* 'my-external-hook))

  *external-hook*
We stored the hook object in a variable since we are going to use itin some other examples.
  (hooks:add-to-hook *external-hook*
                     (lambda (x)
                       (format t "my-external-hook called with argument ~S~%" x)))

  (hooks:run-hook *external-hook* 1)

3.5Hook Combination

Hook combination refers to the different possible way ofconstructing the resulting value of running a hook. While bearing astrong resemblance to method combination in CLOS namewise, hookcombination is a much more restricted and less powerful concept.

The default hook combination is progn:

  (hooks:hook-combination (hooks:external-hook *my-object* 'my-external-hook))
progn hook combination means the final result is the return valueof the handler run last:TODO

Let's set up the hook to test some other combinations

  (hooks:clear-hook *external-hook*)
  (hooks:add-to-hook *external-hook* #'(lambda (x) (mod x 5)))
  (hooks:add-to-hook *external-hook* #'(lambda (x) (- x)))
Combination using list
  (setf (hooks:hook-combination *external-hook*) #'list)

  (list
   (hooks:run-hook *external-hook* -3)
   (hooks:run-hook *external-hook* 1)
   (hooks:run-hook *external-hook* 7))
Combination using max
  (setf (hooks:hook-combination *external-hook*) #'max)

  (list
   (hooks:run-hook *external-hook* -3)
   (hooks:run-hook *external-hook* 1)
   (hooks:run-hook *external-hook* 7))

Note:

Some functions can be used for hook combination, but will not work as expected in all cases. max is one such examples. Running a hook with max hook combination that does not have any handlers will result in an error because max cannot be called without any arguments (which is the result of calling zero handlers).

4Tracking State

  (defmethod hooks:on-become-active :after ((hook t))
    (format t "hook ~S is now active~%" hook))

  (defmethod hooks:on-become-inactive :after ((hook t))
    (format t "hook ~S is now inactive~%" hook))

  (setf *my-object* (make-instance 'my-class))

  (hooks:add-to-hook (hooks:object-hook *my-object* 'my-hook) (lambda (x)))

  (setf (hooks:hook-handlers (hooks:object-hook *my-object* 'my-hook)) nil)

5Restarts

This library uses restart to recover from errors during theexecution of hooks or their handlers. This section briefly discussesthe restarts that are installed at the hook and handler levels.

5.1Hook Restarts

retry
When this restart is invoked, the hook is ran again.
use-value
When this restart is invoked, the hook is not ranand a replacement value is read interactively and returned inplace of the result of running the hook.

5.2Handler Restarts

retry
When this restart is invoked, the handler is executedagain.
use-value
When this restart is invoked, the handler is notexecuted and a replacement value is read interactively andreturned in place of the result of executing the handler.
skip
When this restart is invoked, the handler is skippedwithout producing any return value. If there are otherhandlers, the hook may still produce a return value.

6Convenience Marcos

  (hooks:with-handlers
      (((hooks:external-hook *my-object* 'my-hook)
        (lambda (x)))

       ((hooks:external-hook *my-object* 'my-other-hook)
        (lambda (y z))))
    (hooks:run-hook (hooks:external-hook *my-object* 'my-hook)))

7settings :noexport:

Dependencies (5)

  • alexandria
  • closer-mop
  • fiveam
  • let-plus
  • trivial-garbage
  • GitHub
  • Quicklisp