In the post at Common Lisp class hierarchy, Rainer Joswig & Joshua Taylor carefully distinguish some of the differences between built-in Common Lisp types and classes, where classes form part of the CLOS extension to the baseline. The type/class (plus mop) distinctions are also reflected in Pfeil's comprehensive hierarchy diagram. Using the diagram, it seems possible to extract the two distinct hierarchies. In particular, I am currently most interested in the tops of the hierarchies; namely, the direct subtypes and subclasses of t
(since t
is both a type and a class). Here are some provisional subtypes/subclasses distilled from the diagram:
For the type hierarchy, the direct subtypes of t
seem to be atom
, character
, random-state
, hash-table
, restart
, readtable
, package
, pathname
, stream
, function
, array
, sequence
, number
, and condition
. All other types like float
or list
are subtypes of one of these types. The type hierarchy is also not strictly hierarchical (since (subtype t t) => T
, but this seems to be the only exception). (ps: the types symbol
and structure-object
are not included in the diagram, but also may be direct subtypes of t
.)
For the class hierarchy, the direct subclasses of t
include corresponding classes for all the types above (except atom
, and perhaps structure-object
which is now a subclass of standard-object
(?)), plus standard-object
.
The MOP extends CLOS by adding the class metaobject
(plus some meta-subclasses), but does not seem to add to the direct subclasses of t
.
Can someone verify if this understanding is correct, or provide additional clarifications?
Note: I've discovered at least one mistake in the type hierarchy description above. All of the listed subtypes (character
, etc.) are apparently subtypes of atom
, so they are not direct subtypes of t
. The only other direct subtype of t
seems to be sequence
, since a sequence can be a cons
(non-atom). Moreover, symbol
is, in fact, included in the diagram, and is also a subtype of atom
.
Graphing CL Subtypes of T in LispWorks:
Finding the subtypes:
(defun find-synonym-types (type1 types)
(remove-if-not (lambda (type2)
(and (not (eq type1 type2))
(subtypep type1 type2)
(subtypep type2 type1)))
types))
(defun find-all-types-in-packages (packages &key include-nil)
(let ((types nil))
(loop for package in packages
when (find-package package)
do (do-external-symbols (sym (find-package package))
(when (ignore-errors (subtypep sym t))
(let ((synonyms (find-synonym-types sym types)))
(if synonyms
(pushnew sym (get (first synonyms) :type-synonyms))
(pushnew sym types))))))
(if include-nil types (remove nil types))))
(defun direct-subtypes (supertype &key all-types)
(loop with subtypes = (remove supertype (loop for type in all-types
when (subtypep type supertype)
collect type))
for type in subtypes
when (loop for type2 in (remove type subtypes)
never (subtypep type type2))
collect type))
Graphing it:
#+capi
(defun class-color (class-name)
(typecase (find-class class-name)
(standard-class :blue)
(built-in-class :violet)
(structure-class :brown)
(otherwise :red)))
#capi
(defun graph-subtypes-from-t ()
(let ((all-types (find-all-types-in-packages '("CL" "CLOS"))))
(capi:contain
(make-instance
'capi:graph-pane
:roots '(t)
:children-function (lambda (type)
(direct-subtypes type :all-types all-types))
:node-pane-function #'(lambda (graph-pane node)
(declare (ignore graph-pane))
(make-instance
'capi:item-pinboard-object
:text (format
nil "~{~a~^, ~}"
(cons node
(get node :type-synonyms)))
:graphics-args `(:foreground
,(if (find-class node
nil)
(class-color node)
:black))))
:edge-pane-function #'(lambda (self from to)
(declare (ignore self from to))
(make-instance
'capi:arrow-pinboard-object
:graphics-args '(:foreground
:grey))))
:title "Common Lisp Subtypes in LispWorks")))
Graph
The types with corresponding CLOS classes are written in blue and structure classes are brown. The type NIL
is not drawn. Included are types/classes from the CLOS MOP. Some types have more than one name. Also note that this graph is specific to LispWorks, especially what types are actually also structures, CLOS classes, built-in class or something else.
The term direct subtype almost never makes sense.
Why do you think that character
is a direct subtype of t
?
But character
is a subtype of atom
which is a subtype of t
!
Well, maybe character
is a direct subtype of atom
?
Nope, character
is a subtype of (or character number)
which is a subtype of atom
.
So, maybe character
is a direct subtype of (or character number)
?
Nope, character
is a subtype of (or character integer)
which is a subtype of (or character number)
.
You can think of classes as integers and types as rationals - there are no integers between 3 and 4, but there are plenty of rationals between any two different rationals.
You already know that there are types and there are classes in Common Lisp.
Types categorize objects, while classes tag objects. Moreover, an object may be of many types, but it's a direct instance of a single class.
If you ask an object for its type through type-of
, you may get many valid answers. A non-exhaustive example:
(type-of 1)
=> (integer 1 1)
OR => bit
OR => (unsigned-byte 1)
OR => (mod 2)
OR => (integer (0) (2))
OR => (signed-byte 1)
OR => (signed-byte 16)
OR => fixnum
OR => integer
OR => (integer * *)
OR => rational
OR => real
OR => number
OR => t
OR => atom
For typep
and subtypep
, things are a lot more confusing, as you can generate types at will, like (eql 1)
, (member 1)
, (or fixnum bignum)
, (and (integer * 1) (integer 1 *))
, (not (satisfies zerop))
, (satisfies oddp)
, etc. for true results, and likewise for false results.
However, for each object, you have exactly one class:
(class-of 1)
=> #<BUILT-IN-CLASS INTEGER>
In practice, you can't determine the whole type hierarchy. There is the notion that anything is t
, nothing is nil
and there might be an hierarchy for atomic standard types. But the reality is that there are compound types and user-defined types, so it's technically impossible to draw a full type hierarchy, as that would require to look at the result of subtypep
for every imaginable object.
Even so, there might not exist a hierarchy between types. Taking the mistake you say you found, I believe the author did not want to litter the diagram with arrows pointing to atom
for every type other than cons
. But in fact, that would probably not be a correct representation at all, since some objects of type list
and sequence
are cons
and others are atom
, specifically, the empty list is atom
.
However, you can determine the whole class hierarchy, even though a class may have multiple superclasses.
Some implementations return the fixnum
class in the class-of
example, an extension to the standard (fixnum
is a type but not a class), to allow CLOS method specialization on fixnum
and/or to allow optimization tricks.
From a few implementations I tried, only CLISP returned the integer
class.