Skip to content

LiamHowley/cl-comp

Repository files navigation

CL-COMP

Introduction

CL-COMP is an acronym for Common Lisp Context Oriented Metaobject Protocol. It is a thin wrapper around CONTEXTL. CONTEXTL is a context oriented extension of the Common Lisp Object System, (CLOS), using a meta object protocol, (MOP), to create a metaclass that allows for layered classes and their initialization in multiple defined contexts. CL-COMP extends CONTEXTL by means of a protocol for defining layered metaclasses. As CONTEXTL employs the concept of a defining metaclass, CL-COMP does likewise by using alternate defining metaclasses for each layered definition of the metaclass COMP-BASE-CLASS. By default it uses the defining metaclass BASE-CLASS, and is defined in the layered context COMP-BASE-LAYER. The protocol can be employed in a horizontal or vertical fashion: in the former case by creating additional context layers and redefining COMP-BASE-CLASS in its new context; in the later, by inheriting from BASE-CLASS, COMP-BASE-CLASS, and COMP-BASE-LAYER, to create application specific hierarchies and contexts. Thus context oriented dispatch can be employed not only on class type, but also on metaclass type.

Protocol

Define layer context

As context layers are classes they can inherit from other context layers and use user defined metaclasses. The use of user defined metaclasses enables dispatching on layers by type. Thus the layered functions ADJOIN-LAYER-USING-CLASS and REMOVE-LAYER-USING-CLASS can be called to enable mutual exclusion, complementary inclusion, or various combinations of each, between layers. Layers inheriting from COMP-BASE-LAYER are by default mutually exclusive.

(defclass foo-metaclass (comp-layer-context)
  ())

(deflayer foo-context (comp-base-layer)
  ()
  (:metaclass foo-metaclass))

In the above definitions, FOO-METACLASS and FOO-CONTEXT are subtypes of COMP-LAYER-CONTEXT and COMP-BASE-LAYER respectively. Thus they inherit the rules established in the CL-COMP protocol.

Create defining metaclass

Using DEFCLASS or, if a specific layered context is desired, DEFINE-LAYERED-CLASS, create a class definition that inherits from BASE-CLASS

(define-layered-class bar
  :in foo-context (base-class)
  ((baz :initarg :baz :reader slot-definition-baz)))

As bar is defined as a layered class all the functionality associated with layered classes are available. BAZ for example could have been declared special, or a layered slot, whilst slot-definition-baz could have been created as a layered reader. Source documentation on such features can be found in documentation listed below.

Append initargs for metaclass slots

In order for the initarg :baz to be valid in any subsequent layered metaclasses it must be registered.

(defmethod partial-class-base-initargs append ((class bar))
  '(:baz))

Create slot definition class

Define a class that inherits from COMP-DIRECT-SLOT-DEFINITION.

(defclass foo-context-slot-definition (comp-direct-slot-definition)
  ((important :initarg :important :reader importantp)))

Register slot definition class in layer context

The MOP method DIRECT-SLOT-DEFINITION-CLASS will look for the slot definition class to use. Should it not be defined all slots will be of type STANDARD-DIRECT-SLOT-DEFINITION.

(defmethod slot-definition-class ((layer-metaclass foo-metaclass))
  'foo-context-slot-definition)

Define-layered metaclass

As COMP-BASE-CLASS is a layered-class, the choice when defining the metaclass is to create a sibling class in a different layer, as in:

(define-layered-class comp-base-class
  :in-layer foo-context (bar)
  ())

or to define a metaclass that inherits COMP-BASE-CLASS but exists for the context of your application alone:

(define-layered-class foobar
  :in-layer foo-context (comp-base-class bar)
  ())

Defining a class

The macro DEFINE-BASE-CLASS is a wrapper on the macro DEFINE-LAYERED-CLASS, with a default metaclass of COMP-BASE-CLASS. Slots can be passed as a combined list of symbols and cons. Initargs are automatically created, unless one is provided, using the keyword symbol of the slot name. The class definition is compiled within the layered environment of the definition, i.e. if the layer of a class is FOO-CONTEXT the class will be defined within the active layer FOO-CONTEXT. This aspect is relevant to the initialization process of the class definition. An additional and important note, is the use of a predefined singleton SERIALIZE to finalize inheritance and enable dispatching on class definitions. DEFINE-BASE-CLASS searches for an instance of serialize in the class precedence of any defined superclasses, before adding it to the list of superclasses.

(define-base-class class-in-context 
  :in foo-context ()
  ((slot-in-context :initarg :slot-in-context :initform nil))
  (:metaclass foobar))

#<FOOBAR CL-COMP::CLASS-IN-CONTEXT>

Initializing the class definition - important!

As layered classes are subclasses of STANDARD-CLASS, initialization protocols proceed as per normal. As such, context specific initialization procedures should not be placed within initialize-instance, reinitialize-instance or shared-initialize methods, as they are called for each context in which a class is defined. To put it simply, they are not thread safe. Instead the layered function INITIALIZE-IN-CONTEXT is called from the auxiliary :around method of shared-initialize, and after the call to call-next-method. Context and class specific initialization procedures should be placed in specialized instances of this layered function. It is for this reason that layered classes of type COMP-BASE-CLASS are defined within their layer context.

Introspection & utility functions / macros

Utility functions provided break down into cached functions for introspection, mapping functions, macros that create and access contextual environments and one or two useful helper functions.

Introspection

Taking the class defined above CLASS-IN-CONTEXT, lets first verify that it contains the slot BAZ.

(slot-exists-p (find-class 'class-in-context) 'baz) => T

Now lets create another layer within it’s own context:

(defclass app-metaclass (comp-layer-context)
  ())

(deflayer app-context (comp-base-layer)
  ()
  (:metaclass app-metaclass))

And we’ll follow that with another metaclass following the protocol above:

(define-layered-class defining-app-class
  :in app-context (base-class)
  ((app-template :initform nil :initarg :template :reader template)))

(defmethod partial-class-base-initargs append ((class defining-app-class))
  '(:template))

(defclass application-slot-definition (comp-direct-slot-definition)
  ((important :initarg :important :initform nil :reader importantp)
   (secure :initarg :secure :initform t :reader securep)))

(defmethod slot-definition-class ((layer-metaclass app-metaclass))
  'application-slot-definition)

(define-layered-class app-class
  :in app-context (comp-base-class defining-app-class)
  ())

(define-layered-class app-class
  :in foo-context (foobar)
  ())

Now we can create our layered class definitions.

(define-base-class app-in-context 
  :in foo-context ()
  ((slot-in-context :initarg :slot-in-context :initform nil))
  (:metaclass app-class))

=> #<APP-CLASS CL-COMP::APP-IN-CONTEXT>

(define-base-class app-in-context 
  :in app-context ()
  ((slot-in-context :initarg :slot-in-context :initform nil))
  (:metaclass app-class)
  (:template . #P"/template"))

=> #<APP-CLASS CL-COMP::APP-IN-CONTEXT>

(slot-value (find-class 'app-in-context) 'app-template)

=> #P"/template"

If using the function FILTER-SLOTS-BY-TYPE to request all slots of class app-in-context we find there are two slots named slot-in-context of type FOO-CONTEXT-SLOT-DEFINITION and APPLICATION-SLOT-DEFINITION. An important thing to note here is that FILTER-SLOTS-BY-TYPE and it’s counterpart below FIND-SLOT-DEFINITION create and call on a cached store of direct slots definitions that are derived from the specified class and its precedents. I.e. they also return the direct slot definitions of inherited slots.

(filter-slots-by-type (find-class 'app-in-context) 'comp-direct-slot-definition)

=> '(#<FOO-CONTEXT-SLOT-DEFINITION CL-COMP::SLOT-IN-CONTEXT>
     #<APPLICATION-SLOT-DEFINITION CL-COMP::SLOT-IN-CONTEXT>)

When a single typed slot is required:

(find-slot-definition (find-class 'app-in-context) 'slot-in-context 
		      'application-slot-definition) 

=> #<APPLICATION-SLOT-DEFINITION CL-COMP::SLOT-IN-CONTEXT>

(importantp *) => NIL 
(securep **) => T

(find-slot-definition (find-class 'app-in-context) 'slot-in-context 
		      'foo-context-slot-definition) 

=> #<FOO-CONTEXT-SLOT-DEFINITION CL-COMP::SLOT-IN-CONTEXT>

(importantp *) => CONDITION of type UNBOUND-SLOT 
(securep **) => CONDITION of type NO-APPLICABLE-METHOD-ERROR

Utilities

Function class-precedents class => List of class precedents

Description: Find all superclasses in the inheritance hierarchy of a class. Results are cached unless nil.

Function find-class-precedent class precedent type => Class

Description: Find the precedent (name) in the list of precedents of class, by type. When present, returns the precedent class. Result is drawn from a cached list.

Function filter-precedents-by-type class &optional filter => List of class precedents

Description: Retrieve the prededents of a class filtered by type. Calls map-filtered-precedents and unless nil caches the result.

Function map-filtered-precedents class &optional filter map => List of mapped class precedents.

Description: Map a function to the precedents of a class that may be optionally filtered by type.

Function all-slots class => List of direct slot definitions

Description: Returns all direct slot definitions including those of class precedents. Result is cached unless nil.

Function map-filtered-slots class &optional filter map => List of mapped direct slots definitions.

Description: Map a function to the direct slots of a class, including inherited slots, that may optionally be filtered by type.

Function filter-slots-by-type class type => List of direct slot definitions.

Description: Returns the direct slot definitions of all slots of type associated with a class, including inherited slots. Recursively walks through superclasses. Results are cached unless nil.

Function find-slot-definition class slot-name type => Direct slot definition.

Description: Return and cache slot definition of slot-name and type. Results are cached unless nil.

Macro define-class-context (env class-type active-layers (&rest rest &key &allow-other-keys) &body body) => Captured dynamic environment.

Description: Sets and captures the dynamic environment for specific contexts. Both ENV and CLASS-TYPE are symbols with the former dynamically bound within the macro body. The symbol set to the ENV variable is bound by the captured environment and returned. This captured environment encapsulates active layers and dynamic (special) values of class slot accessors, set through keyword args. To be used in conjunction with the WITH-CLASS-IN-CONTEXT macro.

Macro with-class-in-context (env class-instance &body body)

Description: Facilitates access to the dynamic context bound to ENV. ENV is set by the DEFINE-CLASS-CONTEXT macro.

Macro define-layer-context (env active-layers bindings &body body) => Captured dynamic environment.

Description: Sets and captures the dynamic environment for specific contexts and bindings. The symbol set by the ENV variable is bound to the captured environment and returned.

Macro with-context (env &body body)

Description: Facilitates access to the dynamic context bound to ENV. ENV is set by the DEFINE-LAYER-CONTEXT macro.

Macro delete-context env

Description: Delete the captured dynamic environment.

Function clone-object object => Copy of object.

Description: Makes a shallow copy of a clos object.

Function *slots-with-values class &key type filter-if filter-if-not => 1. List of slot definitions. 2. List of slot names.

Description: Walks through list of slots of class. May be filtered by type; filter-if and filter-if-not are expected to be functions and default to (constantly nil) and (constantly t) respectively. Returns the list of slots belonging to class that are bound to a value. For convenience it also returns a list of slot names.

Function object-to-plist object &key filter recurse package use-placeholders => Tree

Description: Recursively walks through an object creating a plist from the initargs and values of it’s slots. The keyword filter expects a symbol bound to a type. Recurse defaults to T. Package defaults to PACKAGE. The purpose of use-placeholders is to allow for a cacheable representation of the class, by using the slot name in lieu of a slot. Only slots with values are listed. The structural model of the data is replicated in the resulting tree.

Function equality object1 object2 => Boolean

Description: Functionally if not structurally equal: i.e. it facilitates equality testing between user-defined objects of different types.

Further Reading

For more on CONTEXTL layers, including reflective activation/deactivation, see:

https://www.p-cos.net/documents/contextl-overview.pdf

https://www.p-cos.net/documents/contextl-soa.pdf

https://www.hirschfeld.org/writings/media/CostanzaHirschfeld_2007_ReflectiveLayerActivationInContextL_AuthorsVersionAcm.pdf

https://www.p-cos.net/documents/special-full.pdf

Additionally, see the test cases at https://github.com/pcostanza/contextl/

Finally, my gratitude goes to Pascal Costanza for both ContextL and Closer-Mop.

About

Common Lisp Context Oriented Metaobject Protocol - An extension of ContextL

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published