λ

DRef Manual

Table of Contents

[in package DREF]

λ

1 Links and Systems

Here is the official repository and the HTML documentation for the latest version.

DRef is bundled in the same repository with PAX, the documentation system.

λ

2 Introduction

What if definitions were first-class objects?

Some defining forms do not create first-class objects. For example, defun creates function objects, but defvar does not create variable objects as no such thing exists. The main purpose of this library is to fill this gap with the introduction of dref objects:

(defvar *my-var* nil
  "This is my var.")
(dref '*my-var* 'variable)
==> #<DREF *MY-VAR* VARIABLE>

drefs just package up a name (*my-var*) and a locative (variable) then check that the definition actually exists:

(dref 'junk 'variable)
.. debugger invoked on LOCATE-ERROR:
..   Could not locate JUNK VARIABLE.

The Basic Operations on definitions in DRef are arglist, docstring and source-location.

(docstring (dref '*my-var* 'variable))
=> "This is my var."

For definitions associated with objects, the definition can be located from the object:

(locate #'print)
==> #<DREF PRINT FUNCTION>

These objects designate their definitions, so (docstring #'print) works. Extending DRef and these operations is possible through Defining Locative Types. It is also possible to define new operations. For example, PAX makes pax:document extensible through pax:document-object*.

Finally, existing definitions can be queried with definitions and dref-apropos:

(definitions 'dref-ext:locate*)
==> (#<DREF LOCATE* GENERIC-FUNCTION>
-->  #<DREF LOCATE* (METHOD NIL (GLOSSARY-TERM))>
-->  #<DREF LOCATE* (METHOD NIL (SECTION))>
-->  #<DREF LOCATE* (METHOD NIL (READTABLE))>
-->  #<DREF LOCATE* (METHOD NIL (PACKAGE))>
-->  #<DREF LOCATE* (METHOD NIL (ASDF/SYSTEM:SYSTEM))>
-->  #<DREF LOCATE* (METHOD NIL (CLASS))>
-->  #<DREF LOCATE* (METHOD NIL (METHOD))>
-->  #<DREF LOCATE* (METHOD NIL (GENERIC-FUNCTION))>
-->  #<DREF LOCATE* (METHOD NIL (FUNCTION))>
-->  #<DREF LOCATE* (METHOD (:AROUND) (T))>
-->  #<DREF LOCATE* (METHOD NIL (T))> #<DREF LOCATE* (METHOD NIL (XREF))>
-->  #<DREF LOCATE* (METHOD NIL (DREF))>)
(dref-apropos 'locate-error :package :dref)
==> (#<DREF LOCATE-ERROR CONDITION> #<DREF LOCATE-ERROR FUNCTION>)

(dref-apropos "ate-err" :package :dref :external-only t)
==> (#<DREF LOCATE-ERROR CONDITION> #<DREF LOCATE-ERROR FUNCTION>)

λ

3 References

After the Introduction, here we get into the details. Of special interest are:

The Basic Operations (arglist, docstring, source-location) know how to deal with references (discussed in the Extending DRef).

λ

3.1 Dissecting References

The following convenience functions are compositions of {locative-type, locative-args} and {xref-locative, dref-locative}.

λ

3.2 References Glossary

λ

4 dtypes

dtypes are to Lisp types what locative types are to classes. A dtype is either

dtypes are used in definitions and dref-apropos to filter the set of definitions as in

(definitions 'print :dtype '(not unknown))
==> (#<DREF PRINT (CLHS FUNCTION)> #<DREF PRINT FUNCTION>)
(dref-apropos "type specifier" :dtype 'pseudo)
==> (#<DREF "1.4.4.6" #1=(CLHS SECTION)> #<DREF "1.4.4.6.1" #1#>
-->  #<DREF "1.4.4.6.2" #1#> #<DREF "1.4.4.6.3" #1#>
-->  #<DREF "1.4.4.6.4" #1#> #<DREF "4.2.3" #1#>
-->  #<DREF "atomic type specifier" #2=(CLHS GLOSSARY-TERM)>
-->  #<DREF "compound type specifier" #2#>
-->  #<DREF "derived type specifier" #2#> #<DREF "type specifier" #2#>)

λ

5 Listing Definitions

λ

6 Basic Operations

The following functions take a single argument, which may be a dref, or an object denoting its own definition (see locate).

λ

7 Basic Locative Types

The following are the locative types supported out of the box. As all locative types, they are named by symbols. When there is a CL type corresponding to the reference's locative type, the references can be resolved to a unique object as is the case in

(resolve (dref 'print 'function))
==> #<FUNCTION PRINT>
=> T

Even if there is no such CL type, the arglist, the docstring, and the source-location of the defining form is usually recorded unless otherwise noted.

The basic locative types and their inheritance structure is loosely based on the doc-type argument of cl:documentation.

λ

7.1 Locatives for Variables

λ

7.2 Locatives for Macros

λ

7.3 Locatives for Functions and Methods

λ

7.4 Locatives for Types and Declarations

λ

7.5 Locatives for the Condition System

λ

7.6 Locatives for Packages and Readtables

λ

7.7 Locatives for Unknown Definitions

λ

7.8 Locatives for DRef Constructs

λ

8 Extending DRef

λ

8.1 Extension Tutorial

Let's see how to tell DRef about new kinds of definitions through the example of the implementation of the class locative. Note that this is a verbatim pax:include of the sources. Please ignore any internal machinery. The first step is to define the locative type:

(define-locative-type class (type)
  "Naturally, CLASS is the locative type for [CLASS][class]es.

  Also, see the related CONDITION locative.")

Then, we make it possible to look up class definitions:

(define-locator class ((class class))
  (make-instance 'class-dref :name (class-name class) :locative 'class))

(define-lookup class (symbol locative-args)
  (unless (and (symbolp symbol)
               (find-class symbol nil))
    (locate-error "~S does not name a class." symbol))
  (make-instance 'class-dref :name symbol :locative 'class))

define-locator makes (locate (find-class 'dref)) work, while define-lookup is for (dref 'dref 'class). Naturally, for locative types that do not define first-class objects, the first method cannot be defined.

Finally, we define a resolve* method to recover the class object from a class-dref. We also specialize docstring* and source-location*:

(defmethod resolve* ((dref class-dref))
  (find-class (dref-name dref)))

(defmethod docstring* ((class class))
  (documentation* class t))

(defmethod source-location* ((dref class-dref))
  (swank-source-location* (resolve dref) (dref-name dref) 'class))

We took advantage of having just made the class locative type being resolveable, by specializing docstring* on the class class. source-location* was specialized on class-dref to demonstrate how this can be done for non-resolveable locative types.

Classes have no arglist, so no arglist* method is needed. In the following, we describe the pieces in detail.

λ

8.2 Locative Type Hierarchy

Locative types form their own hierarchy, that is only superficially similar to the Lisp class hierarchy. The hierarchies of lisp-locative-types and pseudo-locative-types are distinct. That is, the dref-class of a Lisp locative type must not be a subclass of a pseudo one, and vice versa. This is enforced by define-locative-type and define-pseudo-locative-type.

λ

8.3 Defining Locative Types

λ

8.3.1 Symbol Locatives

Let's see how the opaque define-symbol-locative-type and the obscure define-definer-for-symbol-locative-type macros work together to simplify the common task of associating definition with a symbol in a certain context.

λ

8.4 Extending locate

Internally, locate finds an initial dref of its object argument with a lookup or with a locator. This initial dref is then canonicalized with a series of casts. In more detail, the process is as follows.

Else, locate first needs to finds the initial definition.

λ

8.4.1 Initial Definition

locate can find the initial definition in one of two ways:

λ

8.4.2 Canonicalization

The initial definition thus found is then canonicalized so that there is a unique definition under xref=:

(locate #'arglist*)
==> #<DREF ARGLIST* GENERIC-FUNCTION>
(dref 'arglist* 'function)
==> #<DREF ARGLIST* GENERIC-FUNCTION>
(dref 'arglist* 'generic-function)
==> #<DREF ARGLIST* GENERIC-FUNCTION>

Canonicalization is performed by recursively attempting to downcast the current definition to one of its locative-type-direct-subs in a depth-first manner, backtracking if a cast fails.

λ

Default Downcast

Downcasting to direct locative subtypes is performed by default by looking up the definition where the locative type is replaced with its sub while the name and the locative args remain the same.

λ

Cast Name Change

Casts must be careful about changing dref-name.

Their dref argument and the dref returned must have the same dref-name (under equal, see xref=) or it must be possible to upcast the returned value to the dref argument's dref-locative-type.

λ

8.4.3 Defining Lookups, Locators and Casts

As we have seen, the Initial Definition is provided either by a lookup or a locator, then Canonicalization works with casts. Here, we look at how to define these.

Implementation note: All three are currently implemented as methods of generic functions with eql specializers for the locative type, which may easily prove to be problematic down the road. To make future changes easier, the generic function and the methods are hidden behind e.g. the define-lookup and call-lookup macros.

λ

8.5 Extending Everything Else

λ

8.5.1 Definition Properties

Arbitrary data may be associated with definitions. This mechanism is used by arglist*, docstring* and source-location* for easy extension.

The following functions take an xref argument and not a dref(0 1) to allow working with non-canonical or non-existent definitions.

λ

8.6 dref-classes

These are the dref-classes corresponding to Basic Locative Types. They are exported to make it possible to go beyond the Basic Operations (e.g. pax:document-object*). For Defining Locative Types, they are not necessary, as define-locative-type handles inheritance automatically based on its locative-supertypes argument.

for Variables

for Macros

for Functions

for Types and Declarations

for the Condition System

for Packages and Readtables

for Unknown Definitions

for DRef Constructs

λ

8.7 Source Locations

These represent the file or buffer position of a defining form and are returned by the source-location function. For the details, see the Elisp function slime-goto-source-location.