This library lets you build a metaclass which in turn lets you specify extra slot options in its classes. Options may be easily inspected and custom inheritence may be set up. Metaobject Protocol (MOP) is used for the implementation - through closer-mop. Some convenience function for processing slot options are also available.
Possible use case: you want to automatically set up some definitions based on some slots, but you want to have control over it right in the class definition (check out Macrobuilding below).
So, the first step is to define the metaclass and describe what options you want to have.
(def-extra-options-metaclass example-metaclass ()
((start :initform nil ; `:coalescence', by default, is `replace-or-inherit'
:type integer)
(validates :coalescence bound-only-once
:type boolean)))
NOTE: if you plan on instantiating objects of the metaclass in the same file you define that metaclass, ensure to wrap the metaclass definition in an eval-always
macro (part of serapeum), otherwise you will get errors when compiling the file.
For each option, other than its name, you can specify:
type
,initform
(if not set, the option’s value will be unbound, as expected) andcoalescence
(see Inheritence and Coalescence below).
Now, a class with this metaclass will have :start
and :validates
options:
(defclass alpha ()
((zulu :initform 22
:start 55
:validates t))
(:metaclass example-metaclass))
Before you can inspect the class, finalize it first (or simply instantiate its object): (c2mop:ensure-finalized (find-class 'alpha))
. Now, you can access the slot definition:
(let ((zulu-definition (find 'zulu (c2mop:class-slots (find-class 'alpha))
:key #'c2mop:slot-definition-name)))
(values (slot-definition-start zulu-definition)
(slot-definition-validates zulu-definition)))
55, T
As you can see, the def-extra-options-metaclass
has defined slot-definition-start
and slot-definition-validates
.
When inheriting classes, you can control how options will be inherited. To this end, you can specialize on coalesce-options
. These specializations are available out of the box:
replace-or-inherit
(default, simply inherits the value from its most direct superclass)bound-only-once
(once specified, can’t be overriden in subclasses)merge
(lists only, new value merged with all the values of the superclasses)difference
(lists only, set-difference of new value with all values of the superclasses)
For a demonstration, let’s inherit from alpha
:
(defclass beta (alpha)
((zulu :start 66))
(:metaclass example-metaclass))
Now, if you inspect beta
, you will see that start
is now 66
. Note that you can’t set :validates
to NIL
, as that would cause an error in coalesce-options
, because :coalesecence
for this option is bound-only-once
. Also, given that the compiler does type checking (like SBCL with high enough safety), trying to supply a value of a non-compatible type to an option will yield a compile-time error. See example.lisp to see all of these in action.
Now, if you want to write a macro which defines a class and inspects it right away (to build custom definitions based on it), you can do something like this:
(defmacro def-your-thing (name direct-superclasses direct-slots &rest options)
`(progn
;; you will get errors on the first run if you don't have this `eval-when':
(eval-when (:compile-toplevel :load-toplevel :execute)
(defclass ,name ,direct-superclasses
,direct-slots
(:metaclass your-metaclass)
,@options)
(c2mop:ensure-finalized (find-class ',name)))
(your-macro-that-inspects-the-class ,name)))
The downside is that this relies on immediate finalization of the class. If you don’t want that for some reason, you could probably try defining an after method for c2mop:ensure-class-using-class
, but that doesn’t work out in some cases.
See the docstrings for details.
function | purpose |
---|---|
slot-exists-and-bound-p | self-explanatory |
find-slot | find effective slot name in class/class-name |
find-dslot | same as find-slot , but for direct-slots |
all-direct-slot-definitions | yields all slot definitions from the class precedence list (which includes the class itself). |
all-slot-readers / all-slot-writers | use all-direct-slot-definitions to yield all readers/writers from the class precedence list. |
function | purpose |
---|---|
pick-in-slot-def | given option key, finds all values in a slot definition. |
pick-in-slot-defs | as pick-in-slot-def but for many slots. |
remove-from-slot-def | given an option key, removes it from a slot definition. |
ensure-option-in-slot-def | makes sure that a slot option is present in a slot definition and puts it there (with a default) otherwise. |
function | purpose |
---|---|
slot-option-effective-changed-p | see if the newest state of option changed, useful to see if it’s necessary to, say, define a new method or if the inherited one is sufficient (see the docstring) |
slot-option-direct-changed-p | same as slot-option-effective-changed-p , but for direct slots |
function | purpose |
---|---|
ensure-finalized-precedence | ensures that all classes in the precedence list are finalized |
Less than 80 chars per line.
Available on Quicklisp:
(ql:quickload :slot-extra-options)
To run tests, do:
(asdf:test-system :slot-extra-options)
Should work where closer-mop
works.
Tests ran successfully on: SBCL 2.0.11
, ECL 20.4.24
, CCL 1.12
.
LGPL-3.0-or-later