simple clojure-style protocols for common lisp
see test.lisp for simple (if somewhat silly) examples. real usage can be seen in the com.clearly-useful.generic-sequence-interface system.
this system provides a simple implementation of protcols for common lisp inspired by clojure. the interface to this package is intentionally minimal and exports four symbols:
macro defprotocol name [docstring] (option | method-declaration)+
where name is a symbol docstring a docstring option is one of: (:require protocol) (:base-method …) (:eponymous-method t | nil) (:eponymous-generic t | nil) method-declaration is a simple lambda list with an optional docstring (see below)
of the options, :require is the most straightforward. the others are a little funky, but have come in handy for me in practice. options:
- :require other-protocol this option will check at compile time that any object implementing this protocol already implements other-protocol
- :eponymous-generic bool when true, defprotocol will generate a generic function of one argument with the same name as the protocol. default is nil.
- :eponymous-method bool when true, extend-type will generate a method with the same name as the protocol specializing on the implementing type. the method generated functions as identity. this is just a convenience option. useage implies (:eponymous-generic t) defaults to nil.
- :base-method ((param) body) when supplied, generates an unspecialized method with the same name as the protocol, with the form (:method (param) body). implies (:eponymous-generic t) defaults to nil.
example: (defprotocol printable “an object which can be printed my way” (:base-method (object) (error “oops!”)) (:require stringable) (my-package::print-special (object stream) “print object to stream”))
this results in the following:
- definition of the generic function printable, which will function as identity for any object implementing this protocol. any object which does not implement this protocol will raise the error “oops!” had a :base-method not been supplied, a generic not-implemented error would be raised by objects not implementing the protocol.
- a requirement that any object implementing this protocol already implement stringable
- definition of the generic function printable-p which tests whether an object implements the protocol printable
- definition of the type printable, which satisfies printable-p with the documentation “an object which can be printed my way”
- definition of the generic function my-package::print-special with the documentation “print object to stream”
note that the lambda list for a protocol method is restricted to symbols, and may not contain &optional &key &allow-other-keys. this is to simplify the validation of protocol definitions & implementations at compile time, and may be changed in the future.
note also that methods defined with extend-type only specialize on their first parameter, which is of the type implementing the protocol (see below). this is in line with clojure’s implementation, but may not be neccessary for common lisp, as methods are more flexible. a more complex style of definition & implementation of protocols may be implemented in the future to take full advantage of generic function dispatch, but the current simple implementation will continue to work.
macro extend-type class-name [protocol-name, method-definition+]+
example: (extend-type string foo (bar (self other) (frob other self)) quux (svelte (self) etc))
performs simple validation at compile time.
this will result in method definitions for the generic functions bar and svelte specialized on class string for the first parameter.
lambda-lists for method implementations in extend-type may only contain symbols, and do not support &optional &key &allow-other-keys. this is a limitation of the current implementation that may change in the future. they do, however allow the use of the special symbol _ to indicate ignored parameters. the symbol _ may be used multiple times in an implementation, e.g.
(extend-type frobnicator music:instrument (music:play-with-orchestra (_ _ _) ;;frobnicators only know one note. (music:play-note :b-flat 1.0)))
function protocol-extends-class-p protocol-name class-or-class-name true if class-or-class-name has implemented the protcol named by protocol-name
function class-implements-protocol-p class-or-class-name protocol-name same as above.
notes & todo’s
there is currently no way to undefine a protocol, as I don’t know of a portable way to undefine types in common lisp.
if you redefine a protocol in the current implementation, it does not invalidate existing implementors of the protocol, which would be helpful. This will be fixed.