It is my understanding that the memory layout of a Common Lisp object (bitwise tagging is defined by CLOS (classes).
I understand that every class has a corresponding type, but not every type has a corresponding class, because types can be compound (lists). I think that types are like logical constraints, as opposed to classes that are concrete "types" with a tagging scheme.
If this is correct, does the type system serve any other purpose other than being a logical constraint (such as specifying that an integer must be within a certain range, or that an array contains a particular type)?
If this is not correct, what purpose does the type system actually serve in light of CLOS? Thanks.
I think it's important to get away from the implementation of things, and instead concentrate on how the language thinks about them. Clearly the implementation needs to have enough information to know what sort of thing a given object is, and it's going to do that with some kind of 'tag' (which may or may not be some extra bits attached to the object -- some of it might be the leading bits of the address for instance). Below I've called this the 'representational type'. But you really have almost no access to that implementation detail from the language. It's tempting to think that, type-of
tells you something which maps 1-1 onto the representational type, but that's not true: (type-of (cons 1 2)
is permitted to return (cons integer integer)
for instance, and I think it is probably allowed to return (cons integer number)
or (cons (integer 1 1) (integer 2 2))
. It's unlikely that there are distinct representational types for all of these: indeed there can't be since (type-of 1)
can return (integer m n)
for an infinite number of values of m
& n
.
So here's a take on how the language thinks about things, and the differences between classes and types, in CL.
Both the type system and the class system consist of a bounded lattice of types / classes. Being a lattice means that for any pair of objects there is a unique supremum (so, for types, a unique type of which both types are subtypes, and which has no subtypes for which that is true) and infimum (the reverse). Being bounded means there is a top & a bottom type / class.
(type-of 1 (class-of 1))
works, as does (subtypep (class-of 1) '(integer 0 1))
(the answers being t
and nil, t
respectively).Types are ways to denote collections of objects with common properties, but they are not themselves objects: they are, if anything, just names for collections of things -- the language specification calls these 'type specifiers'. In particular there are an infinite number of types: think of the type (integer m n)
for instance. A small number of this infinitude of types correspond to representational types -- the actual information that tells the system what sort of thing something is -- but obviously most of them do not. There may be representational types which do not have corresponding types.
Types in practice serve three purposes I think.
(integer 0 30)
. Indeed, even if the system does not automatically check declarations you can force checks with, say (check-type x '(integer 0 30) ...)
.The second case is interesting. Let's say I have something which I have told the system is of type (double-float 0.0d0)
. This is very unlikely to be more useful in terms of representational type than double-float
would be. But if I take the square root of this thing then knowing this type might be very useful indeed: the system can know that the result is a double-float
, rather than a (complex double-float)
, and those types are extremely unlikely to be representationally the same. So the system can use my type declaration to make inferences in this way (and these inferences can cascade through the program). Note that classes can't do this (at least CL's classes can't), and neither can the representational type of an object: you need more information than that.
So yes, types serve a number of very useful purposes which aren't satisfied by classes.