AnthonyPhine
AnthonyPhine

Reputation: 47

How does deftype treat symbols and why does it differ from defvar/defun

The cell binding of symbols in Common Lisp doesn't seem to extend to (deftype), is it the case that (deftype) adds the [symbol, predicate body] as [key, value] to an associated map used by the environment for type checking?

I can see why the type predicate wouldn't be stored in the function cell but is there a reason there isn't a "special operator" cell or dedicated property-list attribute for type-specifiers?

Upvotes: 1

Views: 127

Answers (2)

user5920214
user5920214

Reputation:

Types are second-class objects in CL: there are no objects which represent types which are available in the standard language: there are only names of types – type specifiers. So there are no accessors for types, obviously. Furthermore, types in CL are not in general named by symbols at all: type specifiers may be symbols, classes, or lists of various kinds.

There are first-class objects which represent some types: classes. Classes are first-class objects in CL, and there is an accessor for classes: find-class. So, for instance

> (typep 1 'integer)
t

> (typep 1 (find-class 'integer))

But (find-class '(integer 0)) is an error, while (typep 1 '(integer 0)) is fine (and true).

Note though that classes are not types: they are type specifiers (see 4.2.3) and only for a limited range of types.

It is implementation-dependent whether symbols have special 'class' slots which find-class accesses, or whether classes are stored elsewhere in some table indexed by symbol. Historically it was obviously the case that the latter was true, but it's at least possible that some implementations now have class slots.

In fact it's implementation-dependent whether symbols have any slots at all: symbol-value, symbol-function, symbol-plist, symbol-package, symbol-name &c may refer to slots in some 'symbol' object, or they may simply refer to tables indexed by symbol, where symbols are just interned strings. And of course, some may do one, and some the other.

A good example of this is symbol-value: for a variable declared special symbol-value retrieves its current dynamic value:

(let ((%x% 1))
  (declare (special %x%))
  (symbol-value '%x%))

Evaluates to 1.

But there are several possible ways for this to work: in a system which uses shallow binding, then the binding of the variable is stored in some slot associated with the symbol, and this slot is modified appropriately as the symbol is bound or unbound, with the old bindings being stored on some stack (see Henry Baker's paper). In a system with deep binding, it isn't, and the binding is retrieved by searching up a stack of bindings. Naïve shallow-binding systems have fairly nasty problems in the presence of multiple threads, while naïve deep binding systems don't. Which strategy is used is entirely implementation-dependent, of course, which means it's entirely implementation-dependent what symbol-value actually does and whether symbols have 'value' slots at all.

I'm pretty sure that all implementations with multithreading do the obvious right thing: in a case like

(let ((%x% 1))
  (declare (special %x%))
  (some-function ...))

Then the symbol-value of %x% is 1 in the dynamic extent of some-function in the appropriate thread only, and it may have some other value or be unbound in all other threads. In an implementation where multiple threads run truly concurrently (on multiple cores for instance) then this means that symbol-value can't be accessing some unique slot of %x%.

Upvotes: 1

Svante
Svante

Reputation: 51501

It is entirely implementation dependent where type information is stored. Some implementation might decide to use the symbol-plist. Another might use some separate table where the symbol is just the key.

I wouldn't read too much into such details.

Upvotes: 2

Related Questions