Skip to content

some-mthfka/slot-extra-options

Repository files navigation

slot-extra-options

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).

Example

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) and
  • coalescence (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.

Inheritence and Coalescence

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.

Macrobuilding

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.

Convenience functions

See the docstrings for details.

Finding and Collecting Slots

functionpurpose
slot-exists-and-bound-pself-explanatory
find-slotfind effective slot name in class/class-name
find-dslotsame as find-slot, but for direct-slots
all-direct-slot-definitionsyields all slot definitions from the class precedence list (which includes the class itself).
all-slot-readers / all-slot-writersuse all-direct-slot-definitions to yield all readers/writers from the class precedence list.

Slot definitions

functionpurpose
pick-in-slot-defgiven option key, finds all values in a slot definition.
pick-in-slot-defsas pick-in-slot-def but for many slots.
remove-from-slot-defgiven an option key, removes it from a slot definition.
ensure-option-in-slot-defmakes sure that a slot option is present in a slot definition and puts it there (with a default) otherwise.

Change detection

functionpurpose
slot-option-effective-changed-psee 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-psame as slot-option-effective-changed-p, but for direct slots

Finalization

functionpurpose
ensure-finalized-precedenceensures that all classes in the precedence list are finalized

Code Guidelines

Less than 80 chars per line.

Installation

Available on Quicklisp:

(ql:quickload :slot-extra-options)

To run tests, do:

(asdf:test-system :slot-extra-options)

Compiler Support

Should work where closer-mop works.

Tests ran successfully on: SBCL 2.0.11, ECL 20.4.24, CCL 1.12.

Licence

LGPL-3.0-or-later

About

Extra options for slots using MOP.

Topics

Resources

License

LGPL-3.0, GPL-3.0 licenses found

Licenses found

LGPL-3.0
COPYING.LESSER
GPL-3.0
COPYING

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published