19.12 Slot accessors
Dylan does allow you to omit definition of a generic function. As we mentioned earlier, if you define a method without also defining a generic function, Dylan implicitly creates a generic function with the most general types for the parameters and return values that are compatible with the method. The most common case of implicit generic functions is for the slot-accessor methods that are created when a new class is defined. Because these generic functions typically have only a single method and are sealed by default (see Section 19.9), the compiler can make extensive optimizations for slot accessors, ideally making slot access no more expensive than an array reference or structure-member access in other languages.
Even when a slot is inherited by subclassing, a good Dylan compiler will use a coloring algorithm to assign slots to the same offset in each subclass, keeping the cost of slot access to a minimum. You can use primary classes (see Section 19.13) to guarantee efficient slot access.When a program defines explicit methods for a slot getter or setter generic function, of course, the overhead is greater.
Comparison with C++: Dylan classes are similar to virtual base classes with virtual data members in that the offsets of their data members are not fixed, and access to the data members can be overridden. See Section B.2 in Appendix B, Dylan Object Model for C and C++ Programmers, for a more detailed analogy. |
In the <sixty-unit> class, we specified an initial value for total-seconds; hence, there is no need to check that the slot has been initialized before it is accessed. In some situations, it may not be feasible to give a default or initial value for a slot. Dylan permits this omission and will ensure that the slot is initialized before that slot is used; of course, this check does not come for free, so it is preferable to provide initial values where possible. In fact, because we always expect to initialize the total-seconds slot when we make a new <sixty-unit>, it would be more accurate to specify <sixty-unit> as follows:
define open abstract class <sixty-unit> (<object>)
slot total-seconds :: <integer>,
required-init-keyword: total-seconds:,
end class <sixty-unit>;
That is, rather than giving the slot an initial value of 0 and an optional init-keyword:, we simply require that the slot be initialized when we make a <sixty-unit> object. Of course, the initial value must obey the type constraint of <integer>. The compiler can still make the inference that the slot will always be initialized and will always have an integer value.
Always initializing slots, either with a default value or required init-keyword, will make slot access efficient.
Finally, in many cases, slots hold values that will not change over the lifetime of each instance (although they may be different values for each instance). In the case of the <sixty-unit> class, we never change the value of total-seconds. When adding two instances, we create a new one to hold the new value, rather than changing one of the argument instances (that way, we do not have to worry about changing an instance that may still be in use by some other part of the program). In such cases, declaring the slot to be constant both documents and enforces this intent. Furthermore, the compiler can often make additional optimizations for slots that are known never to be modified. The final definition of <sixty-unit> is as follows:
define open abstract class <sixty-unit> (<object>)
constant slot total-seconds :: <integer>,
required-init-keyword: total-seconds:,
end class <sixty-unit>;
(The constant declaration is simply shorthand for the slot option setter: #f, meaning that there is no way to set the slot.)




