Common Lisp type vs. class distinctions

2019-07-18 09:53发布

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.

3条回答
一纸荒年 Trace。
2楼-- · 2019-07-18 10:26

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.

查看更多
forever°为你锁心
3楼-- · 2019-07-18 10:40

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.

Named types in Common Lisp, using LispWorks

查看更多
贪生不怕死
4楼-- · 2019-07-18 10:42

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.

查看更多
登录 后发表回答