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 -setter to the name of the getter.

A number of other options are available in slot specifications:

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:

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 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.