Slots ===== In this chapter, we show how to call getters and setters with the function-call syntax, and how to define methods for getters and setters. We show techniques for initializing slots, including slot options and ``initialize`` methods. We describe the different allocations that slots can have. We find a need for symbols, so we describe and use symbols as well. Dot-syntax abbreviation for simple function calls ------------------------------------------------- The dot syntax that we have shown for getters in :ref:`usr-class-getters-setters`, is an abbreviation for a function call. The first expression is an abbreviation for the second expression: .. code-block:: dylan object.function-name function-name(object) The dot syntax used with the assignment operator also is an abbreviation for a function call. The first two expressions are abbreviations for the third expression: .. code-block:: dylan object.name := new-value; name(object) := new-value; name-setter(new-value, object); You can use the dot syntax as an abbreviation for any function call that takes a single argument and returns a single value. For example, in :ref:`offset-methods-on-time-offset`, we defined the following method: .. code-block:: dylan define method past? (time :: ) => (past? :: ) time.total-seconds < 0; end method past?; The following two calls are equivalent: .. code-block:: dylan past?(*my-time-offset*); *my-time-offset*.past?; In the remainder of this book, we use the dot syntax for function calls that return a property of an object (such as the ``past?`` property of a ```` instance), and that take a single argument and return a single value. Getters and setters for slots ----------------------------- As shown in :ref:`usr-class-getters-setters`, when you define a class, Dylan automatically defines a getter method to return the value of a slot, and defines a setter method to change the value of a slot. .. topic:: Performance note: For slot accesses, given accurate type declarations, the compiler can typically optimize away not only the method dispatch, but also the function call, making the executed code just as efficient as it would be in a language such as C, where structure or record slots are accessed directly. See :doc:`perform`. The name of the getter is always the name of the slot. Thus, the getter for the ``total-seconds`` slot is ``total-seconds``. Let’s look at an example of calling a getter. The first expression is an abbreviation for the second expression: .. code-block:: dylan *my-time-of-day*.total-seconds; total-seconds(*my-time-of-day*); The preceding expressions are calls to the getter function named ``total-seconds``. The choice of which syntax to use is purely a matter of personal style. The first syntax is provided for those people who prefer the slightly more concise dot syntax. The second syntax is provided for those people who prefer slot accesses to look like function calls. In this book, we use the dot syntax. By default, the name of the setter is the slot’s name followed by ``-setter``. Thus, the setter for the ``total-seconds`` slot is ``total-seconds-setter``. You can use the ``setter:`` slot option to specify a different name for the setter. The dot-syntax abbreviation for assignment enables you to invoke the setter by using assignment with the name of the getter. For example, the first two expressions are abbreviations for the third expression: .. code-block:: dylan *my-time-of-day*.total-seconds := 180; total-seconds(*my-time-of-day*) := 180; total-seconds-setter(180, *my-time-of-day*); Each of these expressions stores the value ``180`` in the slot named ``total-seconds`` of the object that is the value of the ``*my-time-of-day*`` variable. Most Dylan programmers do not use the syntax of the third expression to call a setter, because it is more verbose than the first and second expressions. However, it is important to know the name of the setter, so that you can define setter methods. For example, to define a method on the setter for the ``total-seconds`` slot, you define it on ``total-seconds-setter``. For an example of a setter method, see `Setter methods`_. If you do not want Dylan to define a setter method for a slot, you can define the slot to be constant, using the ``constant`` slot adjective, or you can give the ``setter: #f`` slot option. For more information about accessing slots, see :ref:`func-slot-references`, and :ref:`func-assignment`. Advantages of accessing slots via generic functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A slot is conceptually like a variable, in that it has a value. But the only way to access a slot’s value is to call a generic function. Using generic functions and methods to gain access to slot values has three important advantages: - Generic functions provide a public interface to the private implementation of a slot. By making the representation of the slot visible to only the methods of the generic functions, you can change the representation without changing any of the users of the information — the callers of the generic functions. In most cases, a compiler can optimize slot references to reduce or eliminate the cost of hiding the implementation. - A subclass can specialize, or filter, references to superclass slots. For example, the classes ```` and ```` inherit the ``direction`` slot from their superclass ````. In `Virtual slots`_, we show how to provide a setter method for the direction slot of ```` that ensures that the value is north or south, and a setter method for the direction slot of ```` that ensures that the value is east or west. - A slot access can involve arbitrary computation. For example, a slot can be *virtual*. See `Virtual slots`_. .. _slots-setter-methods: Setter methods ~~~~~~~~~~~~~~ In most cases, the getter and setter methods that Dylan defines for each slot are perfectly adequate. In certain cases, however, you might want to change the way a getter or setter works. For example, we can define a setter method to solve a problem in our time library. The class ```` inherits the ``total-seconds`` slot from the class ````. The type of the slot is ```` . However, the semantics of ```` state that the ``total-seconds`` should not be less than 0. We can define a setter method for ```` to ensure that the new value for the total-seconds slot is 0 or greater. In our setter method, we will use the type defined in :ref:`classes-examples-types-not-classes`, and repeated here: .. code-block:: dylan // Define nonnegative integers as integers that are >= zero define constant = limited(, min: 0); The setter method is as follows: .. code-block:: dylan define method total-seconds-setter (total-seconds :: , time :: ) => (total-seconds :: ) if (total-seconds >= 0) next-method(); else error("%d is invalid. total-seconds cannot be negative.", total-seconds); end if; end method total-seconds-setter; When the setter for the ``total-seconds`` slot is called with an instance of ````, the preceding method will be invoked, because it is more specific than the method that Dylan generated on the ```` class. If the new value for the ``total-seconds`` slot is valid (that is, is greater than or equal to 0), then this method calls ``next-method``, which invokes the setter method on ````. If the new value is less than 0, an error is signaled. The following example show what happens when you call ``total-seconds-setter`` with a negative value for ``total-seconds``: .. code-block:: dylan-console ? begin let test-time-of-day = make(); test-time-of-day.total-seconds := -15; end; => ERROR: -15 is invalid. total-seconds cannot be negative. This setter method ensures that no one can assign an invalid value to the slot. For completeness, we must also ensure that no one can initialize the slot to an invalid value. The way to do that is to define an ``initialize`` method, as shown in `Initialize methods`_. Considerations for naming slots and other objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A *binding* is an association between a name and an object. For example, there is a binding that associates the name of a constant and the value of the constant. The names of functions, module variables, local variables, and classes are also bindings. There is a potential problem that can occur if you use short names. If a client module uses other modules that also define and export bindings with short names, there is a significant chance that name clashes will occur, with different bindings with the same name being imported from different modules. If you use the Dylan naming conventions, then a variable will not have the same name as a class, a function, or a constant. The naming conventions avoid name clashes between different kinds of objects. A slot is identified by the name of its getter. The getter is visible to all client modules. There is no problem if two getters with the same name are defined by unrelated classes, because the appropriate getter is selected through method dispatch. There is a problem if a getter has the same name as a generic function with an incompatible parameter list or values declaration. (See :ref:`func-parameter-list-congruence`.) When such a problem occurs, the only way to resolve it is to use options to ``define module`` to exclude or rename some of the problem bindings. This solution is undesirable, because it requires work on the part of the author of the client module, who must spot and resolve such clashes, and then use an interface that no longer matches its documentation. Therefore, for getters that you intend to export, it makes sense prevent clashes by considering the name of the slot carefully. One technique is to prefix the name of the property with the name of the class. For example, you might define a ```` class with a slot ``person-name``, instead of the shorter possibility, ``name``. One drawback of this technique is that it might expose too much information about the implementation — that is, the name betrays the class that happens to implement the slot at a particular time, and you have to remember which superclass introduces a property if you are to access that property. There is a compromise between using short names and using the class name as a prefix — you can choose a prefix for a whole group of classes beneath a given class. For example, you might use the prefix ``person-`` for slots of many classes that inherit from the ```` class, including ````, ````, and so on. .. code-block:: dylan define class () slot person-name; slot person-age; end class ; define class () slot person-number; slot person-salary; end class ; define class () slot person-perks; slot person-parking-lot; end class ; Now, in a method on ````, all accesses are consistent, and we do not have to remember where the slots actually originate: .. code-block:: dylan // Method 1 define method person-status (p :: ) => (status :: ) (p.person-perks.evaluation + p.person-salary.evaluation) / p.person-age; end method person-status; If we had defined the classes differently, such that we prefixed each getter with the name of the class that defined it, the method would look like this: .. code-block:: dylan // Method 2 define method person-status (p :: ) => (status :: ) (p.consultant-perks.evaluation + p.employee-salary.evaluation) / p.person-age; end method person-status; Method 2 is more difficult to write and read than is Method 1, and is more fragile. If, at some point, all employees are allocated perks, then the use of the ``consultant-perks`` getter becomes a problem. .. topic:: Comparison with C++: In C++, the class is the namespace of its member functions. In Dylan, the module is the namespace of getters and setters. In general, the module is the namespace of all module bindings, including generic functions; getters and setters are generic functions. .. _slots-initialize-methods: Initialize methods ------------------ Every time you call ``make`` to create an instance of a class, ``make`` calls the ``initialize`` generic function. The purpose of the ``initialize`` generic function is to initialize the instance before it is returned by ``make``. You can customize the initialization by defining a method on ``initialize``. Methods for ``initialize`` receive the instance as the first argument, and receive all keyword arguments given in the call to ``make``. We define an ``initialize`` method: .. code-block:: dylan define method initialize (time :: , #key) next-method(); if (time.total-seconds < 0) error("%d is invalid. total-seconds cannot be negative", time.total-seconds); end if; end method initialize; On line 2, we call ``next-method``. All methods for ``initialize`` should call ``next-method`` as their first action, to allow any less specific initializations (that is, ``initialize`` methods defined on superclasses) to execute first. If you call ``next-method`` as the first action, then, in the rest of the method, you can operate on an instance that has been properly initialized by any ``initialize`` methods of superclasses. If you forget to include the call to ``next-method``, your ``initialize`` method will be operating on an improperly initialized instance. Lines 3 through 6 contain the real action of this method. We check that the value is valid. If it is invalid, we signal an error. The following example shows what happens when ``total-seconds`` is not valid when we are creating an instance: .. code-block:: dylan-console ? make(, total-seconds: -15); => ERROR: -15 is invalid. total-seconds cannot be negative. Slot options for initialization of slots ---------------------------------------- Unlike variables and constants, slots can be *uninitialized*; that is, you can create an instance without initializing all the slots. If you call a getter for a slot that has not been initialized, Dylan signals an error. In the following sections, we describe a variety of techniques for avoiding the problem of accessing an uninitialized slot. The most general technique is to define an ``initialize`` method for a slot, as shown in `Initialize methods`_. A slot can be uninitialized. Once a slot receives a value, however, it will always have a value: There is no way to return a slot to the uninitialized state. Sometimes it is useful to store in a slot a value that means none. To make that possible, you need to define a new type for that slot, as shown in :ref:`classes-examples-types-not-classes`. In Sections `The init-value: slot option`_ through `The init-function: slot option`_, we show techniques for initializing slots. The ``init-value:`` slot option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can use the ``init-value:`` slot option to give a default initial value to a slot: .. code-block:: dylan define abstract class () slot total-seconds :: , init-keyword: total-seconds:, init-value: 0; end class ; When we use ``make`` to create any subclass of ```` (such as ````), and we do not supply the ``total-seconds:`` keyword to ``make``, the ``total-seconds`` slot is initialized to 0. The ``init-value:`` slot option specifies an expression that is evaluated once, before the first instance of the class is made, to yield a value. Every time that an instance is made and the slot needs a default value, this same value is used as the default. In general, a slot receives its default initial value when no init keyword is defined or when the caller does not supply the init-keyword argument to ``make``. The ``required-init-keyword:`` slot option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Instead of giving the slot a default initial value, we can require the caller of ``make`` to supply an init keyword for the slot. The ``required-init-keyword:`` slot option defines a required init keyword. If the caller of ``make`` does not supply the required init keyword, then an error is signaled. .. code-block:: dylan define abstract class () slot total-seconds :: , required-init-keyword: total-seconds:; end class ; The ``total-seconds`` slot is defined in the ```` class. By making ``total-seconds:`` a required init keyword in this class, we make it required for every class that inherits from it, including ``