Chapter 5
Types and Classes
Slots
Slots are the interface to information about instances. They correspond to
the fields or instance variables of other object-oriented
programming languages. By default, each instance of the class has private storage for each
slot, so one instance can have one value in the slot and another instance can have another
value. Some slots are shared among instances, as described
in Slot Allocation
on page 60.
All slot access is performed by function calls.* The method that returns the value of a slot is called the getter method, and the method that sets the value of a slot is called the setter method. The getter and setter methods are added to generic functions. When defining a class, you specify slots by specifying the generic functions to which the getter and setter methods should be added.
For example, the class definition for <point>
might be
define class <point> (<object>) slot horizontal; slot vertical; end class;
This definition indicates that instances of <point>
should have two
slots, horizontal
and vertical
. The getter method for the first
slot is added to the generic function horizontal
, and the getter method for the
second slot is added to the generic function vertical
. The setter method for
the first slot is added to the generic function horizontal-setter
, while the
setter method for the second slot is added to the generic
function vertical-setter.
The following two code fragments are equivalent. Each returns the horizontal coordinate of a point:
horizontal(a-point) a-point.horizontal
The following three code fragments each set the horizontal coordinate of a point to 10:
horizontal-setter(10, my-point) horizontal(my-point) := 10 my-point.horizontal := 10
A slot setter method returns its new value argument.
Slot Inheritance
Slots are inherited from superclasses.
The collection of all the getter and setter generic functions for slots specified in a class or inherited from its superclasses must not contain any duplicates.
If a superclass is inherited through multiple paths, its slots are inherited once. For example, if class A has direct superclasses B and C, and both B and C have D as a direct superclass, A inherits from D both through B and through C, but the slots defined by D are only counted once. Because of this, multiple inheritance does not by itself create any duplicates among the getters and setters.
Note that two classes that specify a slot with the same getter or setter generic function are disjoint —they can never have a common subclass and no object can be an instance of both classes.
Slot Specifications
A slot specification describes a slot.
A slot specification must include the name of the getter of the
slot (i.e., the name of the generic function to which the getter method will be added). This
is how slots are identified. The specification may optionally include the name of the setter
method. If it does not, a default name is generated by appending
to the name of the getter.-setter
A number of other options are available in slot specifications:
- An initial value for the slot may be specified with an init specification.
- An init-keyword may be specified. This allows a value for the slot to be supplied when an instance is created.
- Slot allocation may be specified. This controls whether storage for the slot is allocated in each instance, or some other way.
- A slot may be specifed as constant. There will be no setter for the slot.
- A type may be specified. The value of the slot will be constrained to be an instance of that type.
- A sealing directive may be specified.
See
Define Sealed Domain
on page 135 for a complete description of the sealing constraints imposed by this directive.
For the complete syntax of slot specifications, see the reference entry of define
class
on page 378.
The following example defines a class with three slots, using a variety of slot options.
define class <window> (<view>) slot title :: <string> = "untitled"; slot position :: <point>, init-keyword: window-position:; slot color, init-keyword: color:, init-value: $blue-color; end class <window>;
Init Specifications
An init specification provides a default initial value for a slot. It can do this directly (if it is an init specification of a slot) or it can do it indirectly by providing a default value for an init-keyword (if it is an init specification of an init-keyword).
There are three kinds of init specifications:
- An init value specifies a value that is used to initialize the slot. Each time the slot needs to be initialized, the identical value is used.
- An init function specifies a function to be called to generate a value that is used to initialize the slot. Each time the slot needs to be initialized, the function is called and its value is used. This allows slots to be initialized to fresh values, or to values computed from the current program state.
- An init expression specifies an expression to be executed to generate a value that is used to initialize the slot. Each time the slot needs to be initialized, the expression is executed and its value is used. This allows slots to be initialized to fresh values, or to values computed from the current program state.
Only one init specification may be supplied in a given slot specification, inherited slot specification, or initialization argument specification.
In general, an init-function will be called and an init-expression will be executed only if its value will actually be used.
Init-Keywords
An init-keyword allows the value of a slot to be specified by a keyword argument in the
call to make
when an instance is created. An init-keyword may be optional or
required.
When the value of a slot is provided by a keyword in a call to make
, it is
called an initialization argument.
If an init-keyword is specified, the slot is said to be keyword initializable.
Slot Allocation
Options for slot allocation include instance
,
class
, each-subclass
,
and virtual
.
instance
allocation specifies that each instance gets its
own storage for the slot. This is the default.
class
allocation specifies there is only one storage
location used by all the general instances of the class. All the instances share a single
value for the slot. If the value is changed in one instance, all the instances see the new
value.
each-subclass
allocation specifies that the class gets one storage location
for the slot, to be used by all the direct instances of the class. In addition, every
subclass of the class gets a storage location for the slot, for use by its direct
instances.
virtual
allocation specifies that no storage will be
allocated for the slot. If allocation is virtual
, then it is up to the
programmer to define methods on the getter and setter generic functions to retrieve and
store the value of the slot. Dylan will ensure the existence of generic functions for any
specified getter and setter but will not add any methods to them. A virtual slot cannot
specify an init specification or init-keyword. Any required initialization for the slot must
be performed in a method
on initialize
.
Constant Slots
Specifying a slot as constant is equivalent to specifying setter: #f
. If the
constant adjective is supplied, it is an error to supply an explicit value for
the setter:
keyword in the slot specification. Such slots can only be given
values at instance creation time (with an init specification
or init-keyword).
define class <person> (<being>) constant slot birthplace, required-init-keyword: birthplace:; end class <person>; define class <astronaut> (<person>) constant class slot employer = #"NASA"; end class <astronaut>; define class <hair-trigger> (<object>) constant slot error-if-touched; end class <hair-trigger>;
Specializing Slots
Slots may be specialized by declaring the type of the slot when a class is created. Specializing a slot has the following effects on the getter and setter methods of the slot:
- The automatically defined slot getter method has its single parameter specialized on the class that specified the slot and has a return value declaration that indicates that it returns a single value of the type specified for the slot.
- The automatically defined slot setter method has its instance argument specialized on the class that specified the slot, has its new-value argument specialized on the type specified for the slot, and has a return value declaration that indicates that it returns a single value of the type specified for the slot.
The following example demonstrates how an explicitly defined setter method can be used to
coerce a slot value of the wrong type (<sequence>
) to the right type
(<simple-object-vector>
).
define class <person> (<object>) slot friends :: <simple-object-vector>, init-value: #[]; end class; define method friends-setter (f :: <sequence>, p :: <person>) p.friends := as(<simple-object-vector>, f); f; // return new-value argument end method tom.friends := list(dick, harry);
The assignment expression invokes the method with the new-value parameter specialized
on <sequence>
, which reinvokes the function with a new-value argument
that is a <simple-object-vector>
, which invokes the slot setter
method.
Overriding Slots in Subclasses
Some slot options related to instance initialization can be overridden in subclasses. The
mechanisms for doing this are described
in Inherited Slot
Specifications
on page 67 and
in Initialization Argument
Specifications
on page 68.
Using Slots
Because slots are accessed through methods in generic functions, they appear to clients just like any other methods in generic functions. It is possible for a value to be stored in a slot in instances of one class, but computed from auxiliary values by instances of another class. It is possible to filter the value of a slot when it is retrieved or stored. In all of these cases, the interface to the value is a function call, thus hiding the implementation details from clients.
In the following example, the class <view>
stores position
directly, while <displaced-view>
performs a transformation on the value
of the slot when storing or retrieving it.
define class <view> (<object>) instance slot position; end class; define class <displaced-view> (<view>) end class; define method position (v :: <displaced-view>) // call the inherited method (the raw slot getter) // and transform the result displace-transform (next-method (v)); end method; define method position-setter (new-position, v :: <displaced-view>) // call the inherited method (the raw slot setter) // on the result of untransforming the position next-method (displace-untransform (new-position), v); new-position; // return the new position end method;
In other situations, a programmer will want storage in an instance for a slot value, but will want to perform some auxiliary action whenever the slot is accessed. In this case, the programmer should define two slots: an instance slot to provide the storage and a virtual slot to provide the interface. In general, only the virtual slot will be documented. The instance slot will be an internal implementation used by the virtual slot for storage. An example of such use would be a slot that caches a value.
define class <shape> (<view>) virtual slot image; instance slot cached-image, init-value: #f; ... end class; define method image (shape :: <shape>) cached-image (shape) | (cached-image (shape) := compute-image (shape)); end method; define method image-setter (new-image, shape :: <shape>) cached-image (shape) := new-image; end method;
* This is in contrast to some other languages where slots are accessed through named value references.