4.7 Defining New Type Specifiers

New type specifiers can come into existence in two ways. First, defining a new structure type with defstruct automatically causes the name of the structure to be a new type specifier symbol. Second, the deftype special operator can be used to define new type-specifier abbreviations.

[Macro] deftype name lambda-list [[{declaration}* | doc-string]] {form}*

This is very similar to a defmacro form: name is the symbol that identifies the type specifier being defined, lambda-list is a lambda-list (and may contain &optional and &rest markers), and the forms constitute the body of the expander function. If we view a type specifier list as a list containing the type specifier name and some argument forms, the argument forms (unevaluated) are bound to the corresponding parameters in lambda-list. Then the body forms are evaluated as an implicit progn, and the value of the last form is interpreted as a new type specifier for which the original specifier was an abbreviation. The name is returned as the value of the deftype form.

deftype differs from defmacro in that if no initform is specified for an &optional parameter, the default value is *, not nil.

If the optional documentation string doc-string is present, then it is attached to the name as a documentation string of type type; see documentation.

Here are some examples of the use of deftype:

(deftype mod (n) ‘(integer 0 (,n)))

(deftype list () ’(or null cons))

(deftype square-matrix (&optional type size)
  "SQUARE-MATRIX includes all square two-dimensional arrays."
  ‘(array ,type (,size ,size)))

(square-matrix short-float 7) means (array short-float (7 7))

(square-matrix bit) means (array bit (* *))

(deftype square-matrix (&optional type size)
  "SQUARE-MATRIX includes all square two-dimensional arrays."
  ‘(array ,type (,size ,size)))

(square-matrix short-float 7) означает (array short-float (7 7))

(square-matrix bit) означает (array bit (* *))

If the type name defined by deftype is used simply as a type specifier symbol, it is interpreted as a type specifier list with no argument forms. Thus, in the example above, square-matrix would mean (array * (* *)), the set of two-dimensional arrays. This would unfortunately fail to convey the constraint that the two dimensions be the same; (square-matrix bit) has the same problem. A better definition is

(defun equidimensional (a)
  (or (< (array-rank a) 2)
      (apply #’= (array-dimensions a))))

(deftype square-matrix (&optional type size)
  ‘(and (array ,type (,size ,size))
        (satisfies equidimensional)))

The body of the expander function defined by deftype is implicitly enclosed in a block construct whose name is the same as the name of the defined type. Therefore return-from may be used to exit from the function.

While defining forms normally appear at top level, it is meaningful to place them in non-top-level contexts; deftype must define the expander function within the enclosing lexical environment, not within the global environment.