context-lite

2022-04-01

A CLOS extension to support specializing methods on special/dynamic variables.

Upstream URL

github.com/markasoftware/context-lite

Author

Mark Polyakov

License

MIT
README
Context Lite

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.

1Installation

Context Lite is available on Ultralisp and will soon be available on Quicklisp.

2Usage

Context lite exports 2 macros: defgeneric* and defmethod*. Read the docstrings to learn how touse them. There's also an ensure-generic*-function which is similar toensure-generic-function, but you probably shouldn't use it.

Pulled from tests.lisp:

    (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 case, :argument-precedence-order and :special-variable-precedence-order can be supplied separately. Normal arguments always get precedence over any special variable.

Look in tests.lisp to get a better feel for everything Context Lite can do.

2.1Running Tests

Load the context-lite/test system, then eval (fiveam:run! 'context-lite/test:context-lite).

3Performance

As 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-method in a closure.

TODO: small benchmark

4What works

  • declare-ations and docstrings.
  • Implicit block around method body with correct name.
  • setf functions.
  • call-next-method.
  • 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.

Dependencies (2)

  • closer-mop
  • fiveam

Dependents (0)

    • GitHub
    • Quicklisp