A CLOS extension to support specializing methods on special/dynamic variables.
Context Lite is a Common Lisp library for writing generic functions and methods that specialize on special/dynamic variables. For instance, if you are writing a method that should behave differently on Monday, but you do not wish to pass the day of the week as an explicit argument:
(defvar *day-of-week* nil) (defmethod* motd () () "It's boring today.") (defmethod* motd () ((*day-of-week* (eql 'monday)) "It's Monday!!!") (let ((*day-of-week* 'monday)) (motd) ; => "It's Monday!!!" )
Context Lite is in the same vein as ContextL, but focuses only on generic functions and methods, not class slots or anything else. Additionally, Context Lite thinks in terms of special variables, while ContextL uses "layers", which are less natural in my opinion.
A major feature of Context Lite is that
defmethod* forms can specialize on special variables that
were not mentioned in
defgeneric* or any previous
defmethod*. Thus, a library author who is
exporting a Context Lite generic function need not know which special variables the library's
consumers will specialize on.
Context Lite methods can handle a mix of "normal" arguments and special variables, with separate precedence orders.
1InstallationContext Lite is available on Ultralisp and will soon be available on Quicklisp.
2UsageContext lite exports 2 macros:
defmethod*. Read the docstrings to learn how touse them. There's also an
ensure-generic*-functionwhich is similar to
ensure-generic-function, but you probably shouldn't use it.
(defvar *indicator* nil) (defmethod* do-the-thing (a) () 0) (defmethod* do-the-thing (a) ((*indicator* (eql :active))) 1) (defmethod* do-the-thing ((a string)) ((*indicator* number)) 2) (is (= 0 (do-the-thing "hello"))) (is (= 0 (let ((*indicator* 42)) (do-the-thing 999)))) (is (= 1 (let ((*indicator* :active)) (do-the-thing 5)))) (is (= 2 (let ((*indicator* 42)) (do-the-thing "heyhi"))))
Here's another example, demonstrating custom precedence order among the special variable arguments:
(defvar *a* nil) (defvar *b* nil) (defmethod* do-the-thing () ((*a* (eql 42))) 0) (defmethod* do-the-thing () ((*b* (eql 42))) 1) (is (= 0 (let ((*a* 42) (*b* 42)) (do-the-thing)))) (defgeneric* do-the-thing () (:special-variable-precedence-order *b* *a*)) (is (= 1 (let ((*a* 42) (*b* 42)) (do-the-thing)))))
generic* functions can specialize on both normal arguments and on special variables. In this
:special-variable-precedence-order can be supplied
separately. Normal arguments always get precedence over any special variable.
tests.lisp to get a better feel for everything Context Lite can do.
2.1Running TestsLoad the
context-lite/testsystem, then eval
3PerformanceAs you'd expect, Context Lite internally uses CLOS' method dispatch, so performance should bepretty good. The biggest cost comes when there is a long chain of next-methods, because ContextLite wraps each
next-methodin a closure.
TODO: small benchmark
declare-ations and docstrings.
- Implicit block around method body with correct name.
- Method redefinitions overwrite the old one.
- Argument precedence orders, including interaction between normal and special arguments.
- Tested in SBCL, CLISP, ECL, CCL, and ABCL. Theoretically should work in any fully MOP-compliantimplementation (but there's no such thing).
5What doesn't work
- Impossible to find or remove methods.
- The generic function will signal an error if a special variable used by any method is unbound.