Previous chapter: Modules

Dylan manual: Introduction to Functions and Classes

6. Introduction to Functions and Classes

This section describes the use of functions and classes, the basic tools used in creating Dylan programs.

Generic Functions and Methods

The basic tools for defining generic functions and methods are the macros define-generic-function and define-method. Additional tools for working with generic functions and methods are described in a later section.

define-generic-function is used to declare information about the generic function as a whole. Currently, it is used to describe the arguments accepted by the generic function. It also provides a home base, for documentation purposes. If Dylan is extended to support a more formal notion of protocols, some of this information would be described in define-generic-function forms. It is not strictly necessary to use define-generic-function; generic functions can be created with define-method alone.

define-method is used to add methods to a generic function. It can also be used to create a new generic function that does not already exist.

See Also:Dylan Design Notes: Defining Forms Make Constants (Change)

See Also: Dylan Design Notes: Definitions are Declarative (Change)

define-generic-function[return to first citation] 	name parameter-list  	[Macro]
	keyword1 option1 keyword2 option2 ... 
==>  name
name should be a variable name. If the variable is bound in the current module, then it must contain a generic-function, or an error is signaled. If it contains a generic function, the definition of the generic function must be consistent with the call to define-generic-function, or an error is signaled. If there is no variable with the given name bound in the current module, then a read-only variable with the name is created in the current module, containing a new generic function object, as specified by the parameter-list and options.

define-generic-function returns name.

parameter-list has the syntax

({required}* [#rest rest] [#key {key}*])

The requireds and rest must be variable names. The keys must be symbols or keywords. #key can be specified without specifying any keys.

Specifying #key means that the generic function takes a variable number of arguments. Methods added to the generic function must specify #rest, #key, or both. When the generic function is called, the arguments following the required arguments must be keyword/value pairs, whether or not any of the applicable methods contain #key.

If any keys are specified, then all methods added to the generic function must accept the specified keys (either by specifying them explicitly after #key or by specifying a #rest argument).

Specifying #rest means that the generic function takes a variable number of arguments; it does not however, require that the extra arguments be keyword/value pairs. (They would only have to be keyword/value pairs if any of the applicable methods specify #key). Methods added to the generic function must specify #rest, #key, or both.

The meaning of specifying both #rest and #key in define-generic-function is unspecified.

Currently no options for define-generic-function are supported. The syntax is specified for future expansion. For example, options could be used to specify method-combination, the generic-functions class, return-values, additional argument checking rules, etc.

See Also:Dylan Design Notes: Defining Forms Make Constants (Change)

define-method[return to first citation] variable-name parameter-list [Macro] form1 form2 ... ==> variable-name

define-method creates a method, and adds it to the generic function in variable-name. If the generic function already contains a method with the exact same specialize[next citation]rs as the new method, then the old method is replaced by the new one.

variable-name is returned.

If the module variable variable-name is not already defined, it is first defined (as with define) and a generic function is created and stored in it. Thus, define-method will create a new generic function or extend an old one, as needed.

If the variable variable-name is already defined but contains something besides a generic function, an error is signaled. If variable-name contains an incompatible generic function (e.g., a generic function with an incompatible parameter list), an error is signaled.

See Also: Dylan Design Notes: Result Type Declarations (Addition)

The parameters of the methodand the specializers of the parametersare described in the parameter-list. The specializers declare what kinds of arguments are acceptable to the method. The method can be called only with arguments that match the specializers of the parameters. A complete description of parameter lists is given below.

When the method is run, it executes the forms in order. The forms, taken together, constitute the body of the method.

Within the body of the method, the parameters can be accessed as lexical variables.

The following example shows how a generic function double could be defined to work with some built-in classes.

If you try to call double before it is defined, you get an error.

? (double 10)
error: unbound variable double.
You can define a method for double that is applicable when double is called on a number.

? (define-method double ((thing <number>))
    (+ thing thing))
double
? double
{the generic function double}
? (double 10)
20
The generic function double is now defined, but because it has a method only for the class <number>, it can be called only on instances of <number>. If you try to call double with another class of argument, you will get an error.

? (double "the rain in Spain.")
error: no method for {the generic function double} was found
       for the arguments ("the rain in Spain.")
To operate on a second class of arguments, you have to add another method to double.

? (define-method double ((thing <sequence>))
    (concatenate thing thing))
double
? (double "the rain in Spain.")
"the rain in Spain.the rain in Spain."
? (double '(a b c))
(a b c a b c)
Parameter List Syntax

Dylan supports required parameters, rest parameters[next citation], keyword parameters[next citation], and sometimes a special next-method parameter.

Required parameters correspond to arguments supplied when a function is called. The arguments are supplied in a fixed order and must appear before any other arguments.

A rest parameter allows a function to accept an unlimited number of arguments. FN12 After the required arguments of a function have been supplied, any additional arguments are stored in a sequence, which is passed as the value of the rest parameter.

Keyword parameters correspond to arguments that are optional and may be given in any order. The arguments and parameters are matched up by keyword name. Keyword arguments can only be supplied after all required arguments are supplied.

Required parameters come first in the parameter list, followed by the rest parameter, if any, and then the keyword parameters, if any. A rest parameter is indicated by the token '#rest[next citation]', followed by the name of the parameter. Keyword parameters are indicated by the token '#key[next citation]', followed by the keyword parameter specifiers. A next-method parameter is indicated by the token '#next[return to first citation]', followed by the name of the parameter.

The complete syntax of parameter lists is:

( {required | (required specializer)}*
  [#next next]
  [#rest rest]
  [#key {keyparam | ([keyword] keyparam [default])}*]
  )
required, rest, and next, have the syntax of variable names. The syntax of keyword parameters is described below. Specializers are described below.

It is not normally necessary to specify a next-method parameter explicitly. If a next-method parameter is not specified by the programmer, define-method inserts one with the name next-method. If an explicit next-method parameter is given, it should come after the required parameters and before the rest and keyword parameters. Details of using next-method are given in the section on method combination.

The following are sample parameter lists as they would appear in a method definition.

(a b c)
three required parameters
((a <list>) b #rest c)
two required parameters and a rest parameter
(#rest a)
a rest parameter, no required parameters
(a b #key foo bar)
two required and two keyword parameters
(#key bar baz bim)
three keyword and no required parameters
(#rest all #key fee fi)
a rest parameter and two keyword parameters
Rest Parameters[Return To First Citation]

The following is a sample use of a rest parameters.

? (define-method show-rest (a #rest b)
    (print a)
    (print b)
    #t)
show-rest
? (show-rest 10 20 30 40)
10
(20 30 40)
#t
? (show-rest 10)
10
()
#t
Keyword Parameters[return to first citation]

When defining a method that includes keyword parameters, each keyword specifier must have one of the following forms:

symbol
or
(symbol default)
or
(keyword parameter)
or
(keyword parameter default)
In the first two forms, the symbol is used to indicate both the keyword and the parameter. Because of this, it cannot use complete variable syntax but allows only symbol variables (i.e., it does not allow setter variables). In the last two forms, the keyword and parameter are given independently. The keyword is used when calling the method, and the parameter is used to refer to the value inside the body of the method. The parameter can use full variable syntax (i.e., it can be a setter variable as well as a symbol variable).

The default supplies a default value for the argument. It is used when the method is called and the keyword is not supplied. The default should be an expression. It is evaluated each time the method is called and the corresponding keyword argument is not supplied. If no default is specified, the parameter corresponding to an unsupplied keyword argument is initialized to #f. The default is evaluated in a scope that includes all the preceding parameters, including required parameters, the rest parameter (if any), the preceding keyword parameters, and the next-method parameter (if any).

For example:

(define-method percolate (#key (brand 'maxwell-house)
                               (cups 4)
                               (strength 'strong))
  (make-coffee brand cups strength))

(define-method layout (widget #key (position: the-pos)
                                   (size: the-size))
  (bind ((the-sibling (sibling widget)))
   (unless (= the-pos (position the-sibling))
     (align-objects widget the-sibling the-pos the-size))
These methods could be called as follows:

(percolate brand: 'folgers cups: 10)
(percolate strength: 'weak
           brand: 'tasters-choice
           cups: 1)
(layout my-widget position: (point 10 10)
                  size: (point 30 50))
(layout my-widget size: (query-user-for-size))
The extended syntax for declaring keyword arguments (in which the keyword name and parameter name are given separately) is needed to allow keyword names like position: without forcing the method to use position as a parameter name. If a method uses position as a parameter name, it cannot access the function stored in the module variable position. The lexical variable will shadow the module variable.

All required arguments must be supplied before any keyword arguments can be supplied. In the second call to show-keys, below, the arguments foo: and three are taken as the two required arguments, rather than as a keyword argument because all the required parameters must be filled before any arguments will be used towards keyword parameters.

? (define-method show-keys (req1 req2 #key foo)
    (format #t "requireds: ~a ~a~%" req1 req2)
    (format #t "key: ~a" foo)
    #t)
show-keys
? (show-keys 'one 'two foo: 'three)
requireds: one two
key: three
#t
? (show-keys foo: 'three)
requireds: foo: three
key: #f
#t
Keyword Argument Checking

A method accepts only those keywords it specifically mentions in its parameter list, unless it specifies #rest[return to first citation], in which case it accepts any keyword. (If it specifies #rest and not #key[return to first citation], this still counts as accepting any keyword.) A generic function accepts all the keywords accepted by any of the applicable methods for a given call. This means that if any applicable method specifies #rest, then the generic function will accept all keywords.

All the applicable methods wont necessarily be called (i.e., in cases where next-method is not called). Dylan does not check for that situation.

It is an error to call a method or generic function with a keyword argument that it does not accept (either through #key or #rest). If a method or generic function specifies #key, it signals an error if the arguments following the required arguments are not keyword/value pairs.

If a method is called via a generic function or via next-method (rather than directly), the method itself does not complain about keyword arguments not accepted by its parameter list. In these cases, the generic function does all the checking before dispatching to any method. In addition, Dylan is not required to check whether correct keywords are supplied if next-method is called with changed arguments.

? (define-method label ((x <object>) #key price)
   (list price x))
label
? (define-method label ((x <sequence>) #key unit-price)
   (add x (* unit-price (length x))))
label
? (define-method label ((x <list>) #rest info #key calories)
   (add x calories))
label
? (label 'grape price: 189 unit-price: 2)
error:  illegal keyword argument unit-price:.  Accepted keyword arguments are (price:).
? (label 'grape price: 189)
(189 grape)
? (label (vector 3 4 5) price: 189 unit-price: 2)
#(6 3 4 5)
? (label (vector 3 4 5) protein: 7 fat: 8 calories: 9)
error:  illegal keyword argument protein:.  Accepted keyword arguments are (price: unit-price:).
? (label (list 3 4 5) protein: 7 fat: 8 calories: 9)
(9 3 4 5)
A call to a function may supply the same keyword argument more than once. When this is done, the leftmost occurrence is used.

Combining #rest and #key

If #rest and #key are used in the same parameter list, #rest must come first. The rest parameter will be bound to a sequence containing all the keywords and their corresponding values. The method can be called with any keyword arguments, but it cannot be given rest arguments that are not in keyword format (i.e., are not keyword/value pairs).

? (define-method test (the-req #rest the-rest
                               #key a b)
    (print the-req)
    (print the-rest)
    (print a)
    (print b))
test
? (test 1 a: 2 b: 3 c: 4)
1
(a: 2 b: 3 c: 4)
2
3
Parameter List Congruency

All the methods in and the define-generic-function of a given generic function must have congruent parameter lists. Two parameter lists are congruent if they have the same number of required arguments, and either both specify #rest and/or #key, or neither specifies #rest and/or #key.

The following are examples of groups of congruent parameter lists. None of the parameter lists is congruent with one from any other group.type-checking[next citation]

(a b #key)
(x y #key foo)
(a b #key bar)
(a b #rest l)
(a b #rest l #key quux)

(a b)
(x y)

(a b c)
(d e f)

(a b c #key)
(x y c #key foo)
(a b c #key bar)
(a b c #rest l)
(a b c #rest l #key quux)
See Also: Dylan Design Notes: Type Restrictions Survive Assignment (Change)

Specializing Required Parameters

Required parameters can be specialized for a class or individual object. Specialization restricts the arguments that may be passed as the value of the parameter. The method can be called only with arguments that match the specializers of the corresponding parameters. If a specializer is a class, the corresponding argument must be a general instance of the class. If the specializer is a singleton[next citation] (used to indicate an individual object), the corresponding argument must be the singletons object.

Specialized parameters are used in method dispatch. A generic function chooses among its methods on the basis of the methods specializers. The generic function chooses the method whose specializers most closely match the classes and identities of the actual parameters. reference p. 53

In addition, specializers can make code easier to understand and they provide type information to the compiler.

The following are examples of parameter lists that include specializers:

((a <window>) b c)
Three required arguments. The first must be a window.
((a <window>)
(b <character>)
c)
Three required arguments. The first must be a window and the second a character. The third can be of any class.
((a <window>)
(b <character>)
(c (singleton 0)))
Three required arguments. The first must be a window, the second a character, and the third the integer 0.
(a (b <string>)
#rest c)
Two required arguments and a rest argument. The second argument must be a string.
((a <vector>) b
#key foo bar)
Two required and two keyword arguments. The first required argument must be a vector.
Specializers are evaluated once, when a method is created. They are not reevaluated each time the method or containing generic function is called.

Specializers will usually be module-variables or calls to singleton. However, they are not restricted to these forms. A specializer can be any expression that evaluates to a class or a singleton.

Generic Functions with No Required Parameters

A generic function with no required parameters can contain a single method. Adding a method has the effect of replacing the old method.

Classes, Instances, and Singleton[Next Citation]s

See Also: Dylan Design Notes: Regularization of the Type System (Change)

See Also: Dylan Design Notes: Limited Types (Addition)

See Also: Dylan Design Notes: Union Types (Addition)

Classes are used to categorize Dylan objects. Classes specify the structure of their instances. In concert with methods, classes help determine the behavior of their instances. Every object is a direct instance of exactly one class.

A class determines which slots its instances have. Slots are the local storage available within instances. They are used to store the state of objects. Slots correspond to the fields or instance variables of other object-oriented programming languages.

A class also helps determine the behavior of its instances. When an object is passed as an argument to a generic function, the generic function looks at the class (and perhaps identity) of the object to determine which method should be run.

Singletons are specializers used to indicate an individual object. A singleton for an object is created by passing the object to the function singleton. Singletons are used to customize the behavior of individual objects. Singletons can be used for specialization, like classes, but the specialization will only apply to the singleton object. When determining whether a method is applicable for a set of arguments, an argument with a singleton specializer must be id? to the object used to create the singleton.

Basic information on classes is given in this section. Additional information is given in Chapter 11.

Slots

Slots are used to store the state of instances. Usually, all the slots in an instance are specified in the definition of the instances class, though it is also possible to add slots to a class or an individual instance. For most slots, each instance of the class has private storage for the slot, so one instance can have one value in the slot and another instance can have another value.

Slots are accessed through methods. 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. Normally the getter and setter methods are added to generic functions. When defining a class, you specify slots by specifying to which generic functions the getter and setter methods should be added.

For example, the class definition for <point> might be

(define-class <point> (<object>)
  horizontal
  vertical)
This definition indicates that instances of <point> should have two slots. 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 (setter horizontal), while the setter method for the second slot is added to the generic function (setter vertical).

To get the horizontal coordinate of a point, make the call

(horizontal my-point)
To set the horizontal coordinate to 10, use the corresponding setter function:

((setter horizontal) my-point 10)
or

(set! (horizontal my-point) 10)
(The full syntax for setter functions is described below, in the section on set!.)

Rationale

Many object-oriented languages treat slots as variables and use the syntax of variable reference to access slots. Dylan follows the lead of some newer object-oriented languages by always accessing slots through function call (or message send) syntax. FN13

Consistent function call syntax helps hides implementation details. For example, the end-point value may be stored in a slot in one class and computed from location and size in another. Both classes provide the same interface to end-point (the end-point generic function). The first class has added a slot getter method to end-point, and the second class has added a method that computes the value. If the implementor of either class decides to change the implementation, users of the classes will not need to change or recompile code, because there is no change in the interface.

Further discussion of slots is given in the following sections.

Defining New Classes

The double example given in the section Generic Functions and Methods showed how to define methods for classes that already exist. A large portion of Dylan programming consists of defining new classes. New classes are created with the macro define-class.

The creation and initialization of instances is controlled by the generic functions initialize and make.

See Also: Dylan Design Notes: Definitions are Declarative (Change)

define-class[return to first citation]	class-name (superclass1 superclass2...)	[Macro]
	slot-spec1 slot-spec2...
==>  class-name
define-class is used to create classes. define-class defines the module variable class-name and creates a class to store in the variable. The variable is made readonly.

The new class inherits from the list of superclasses[return to first citation]. The elements of the list are evaluated. FN14 The list cannot contain duplicates, and the class heterarchy cannot be circularthat is, a class cannot be its own direct or indirect superclass. At least one superclass must be specified.

If the class-name argument to define-class already names a variable in the current module, and if the variable contains a class, define-class modifies the class in place. (That is, it does not create a new class, but modifies the class which already exists.) In this way, anything that references the class will reference the modified version. Existing instances of the class are updated to the new class definition at some point before they are next accessed.

If the variable class-name exists but contains something besides a class, an error is signaled.

In addition to inheriting slots from its superclasses, the new class is given a slot for each of the slot-spec arguments. In the simplest format, a slot-spec is just a symbol. A getter method is defined on the generic function symbol, and a setter function is defined on the generic function (setter symbol). The full syntax for slot-specs is given in a separate section below. The full syntax allows many more options when defining slots.

The following definition creates a new class and stores it in the module variable <menu>. Instances of the class will have two slots. The first slot is read with the generic function title and set with the generic function (setter title). The second slot is read with the generic function action and set with the generic function (setter action).

? (define-class <menu> (<object>)
    title
    action)
make[return to first citation]   class #rest key-value-pairs   ==>  instance	[Generic Function]
Make returns an instance of class, with characteristics specified by key-value-pairs.

Dylan does not specify whether the value returned must be a newly allocated instance, or whether make is permitted to return a previously created instance. If a new instance is allocated, make will call initialize on the instance before returning it. The new instance and the key-value-pairs are passed to initialize.

The keywords arguments that can be specified in key-value-pairs include the keyword arguments accepted by initialize methods of the class and its superclasses, as well as the init-keywords specified for slots in the class and its superclasses. Init-keywords are described below.

The object returned is guaranteed to be a general instance of class but not necessarily a direct instance of class. This liberality allows make to be called on an abstract class; it can instantiate and return a direct instance of one of the concrete subclasses of the abstract class. FN15

Programmers do not normally add methods to make. Instead, they add methods to initialize.

initialize[return to first citation]   instance #key...   ==>  {unspecified}	[Generic Function]
initialize is used to perform class-specific initialization of new instances. Programmers should add methods to this generic function, to perform any initializations specific to classes they define.

Slot Options

Each slot-spec in a define-class form can have one of the following forms:

slot-name
or
(slot-name {keyword value}*)
or
({keyword value}+)
The slot-name argument is not evaluated. It must be a symbol. It provides default values for two of the keyword arguments. The slot-name is not otherwise used, except perhaps for debugging purposes.

Either slot-name or the getter: keyword must be specified.

The possible keywords are:

Each keyword can be specified no more than once and must be followed by a value. Implementations may add additional keyword arguments.

getter:
The name of a module variable to which the getter method should be added. If the slot-name argument is given, it is used as the default value for the getter: argument; otherwise there is no default. This argument is not evaluated.
setter:
The name of a module variable to which the setter method should be added. If the slot-name argument is given, then (setter slot-name) is used as the default value for the setter: argument; otherwise there is no default. This argument is not evaluated.
type:
Should be a type-specifier that limits the types of values that can be stored in the slot. This type specification is enforced by the low-level slot storage mechanisms. It is not enforced for virtual slots, which do not use the low-level slot storage mechanisms.
This argument defaults to <object>, which allows any object to be stored in the slot. The argument is evaluated once, after the variable containing the class is defined and before the slot is first added to an instance.
Currently, the only type specifiers supported are specializers (classes and singletons).
init-value:
Supplies a default initial value for the slot. The argument is evaluated once, after the variable containing the class is defined and before the slot is first added to an instance. The resultant value is used as the initial value when an instance is created. If you want to create a new value for every instance, you should supply an init-function rather than an init-value. There is no default value for this argument.
init-value: may not be specified with init-function:.
init-function:
Should be a function of no arguments. It will be called to generate an initial value for the slot when a new instance is created. There is no default value for this argument. The argument is evaluated once, after the variable containing the class is defined and before the slot is first added to an instance.
init-function: may not be specified with init-value:.
init-keyword:
Should be a keyword. It permits an initial value for the slot to be passed to make, as a keyword argument using this keyword. For use, see Slot Initialization, below. This argument is not evaluated.
There is no default for the init-keyword: argument. If none is specified, there will be no init-keyword for the slot.
init-keyword: and required-init-keyword: cannot both be specified for a single slot.
required-init-keyword:
Like init-keyword:, except it indicates an init-keyword that must be provided when the class is instantiated. If make is called on the class and a required init-keyword is not provided, an error is signaled. FN16 If required-init-keyword: is specified for a slot, then init-keyword:, init-value:, and init-function: cannot be specified.
allocation:
Specifies how storage for the slot is allocated. This argument should be one of the symbols instance, class, each-subclass, virtual, or constant, or it may be an implementation-dependent value. This argument is not evaluated.

instance
Indicates that each instance gets its own storage for the slot. This is the default.
class
Indicates there is only one storage location used by all the direct and indirect 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
Indicates 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.
constant
Indicates a constant value for the slot. There will be no setter method. The value of the slot must be specified as an init-value.
virtual
Indicates that no storage will be automatically 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.
? (define-class <rectangle> (<object>)
     (top type: <integer>
          init-value: 0
          init-keyword: top:)
     (left type: <integer>
           init-value: 0
           init-keyword: left:)
     (bottom type: <integer>
             init-value: 100
             init-keyword: bottom:)
     (right type: <integer>
            init-value: 100
            init-keyword: right:))
<rectangle>
? <rectangle>
{the class <rectangle>}
? (define my-rectangle (make <rectangle> top: 50 left: 50))
my-rectangle
? (top my-rectangle)
50
? (bottom my-rectangle)
100
? (set! (bottom my-rectangle) 55)
55
? (bottom my-rectangle)
55
? (set! (bottom my-rectangle) 'foo)
error: foo is not an instance of <integer> while executing (setter bottom).
Slot Initialization

The initialization protocol varies slightly with the allocation type of the slot. Initialization for instance allocation is described first, followed by the differences for other allocation types.

When a slot is defined, the init-value:, init-function:, init- keyword:, and required-init-keyword: arguments provide four different ways to initialize the value of the slot in new instances.

An init-value or init-function is used to provide a default value for a slot. An init-keyword lets the caller of make supply an initial slot value when a new instance is created. Because both init-value and init-function serve the same role, they cannot both be specified in a slot definition. However, either is compatible with specifying an init-keyword.

When make is called to create a new instance, the values of some of the slots can be specified as keyword arguments to make. For example, if the class <auto> has a slot with the init-keyword color:, a red <auto> could be created with the call

(make <auto> color: 'red)
If the call to make does not include an init-keyword for a given slot, then the slot is initialized from the init-value or the init-function. (Dylan guarantees that the init-function will not be called if the init-keyword is supplied.) If there is no init-value or init-function, the slot remains uninitialized. An error is signaled if a program tries to get the value of an uninitialized slot. The slot must be set before it can be gotten.

An init-value is a single value that is used over and over again to initialize instances. In contrast, an init-function is a function that is called whenever a default value is needed. The init-function can compute or allocate a new value for each instance. For example, an init-function is appropriate if you want the slot to contain an empty data structure, such as an empty array, that will later be used as storage by the object. An init-value is appropriate for initializing the slot to a simple value, such as #f or the integer 0, or to initialize the slot to a particular known object.

Regardless of the source, the initial value is stored directly into the slot using an unspecified built-in mechanism. In particular, the setter generic function is not called to store the value into the slot.

For slots that are shared among instancesthat is, for slots with allocation of class or each-subclassthe initial value can be specified only as an init-value.

For slots with allocation constant, the value must be supplied as an init-value when the slot is defined.

For slots with allocation virtual, the responsibility for any needed initialization lies with the programmer. For example, the slot may be initialized in a method for initialize. Because there is no automatic initialization, the slot-options init-value:, init-function:, init-keyword:, and required-init-keyword: cannot be supplied when defining virtual slots.

Testing the Initialization of a Slot

A program can test to see whether a slot has been initialized.

slot-initialized?[return to first citation]?   instance getter   X  boolean	[Generic Function]
slot-initialized? returns true if the slot in instance that would be accessed by the getter generic function is initialized. If the slot is not initialized, then false is returned.

The default version of the function will always return true for virtual slots. If you create a virtual slot and want to define an uninitialized state for it, you should add a method to slot-initialized? which specializes on your class and on a singleton of the generic function for the slot.

slot-initialized? will signal an error if the getter does not access a slot in the instance.

In many implementations, slot-initialized? will be a relatively expensive operation.

There is no portable method for resetting a slot to the uninitialized state.

Adding Slots After Instance Creation

Slots can be added to classes and individual instances even after instance creation. If a slot is added to a class, then all the instances of the class are updated to contain the new slot. The exact time at which the instances are updated is not defined. However, they are guaranteed to be updated by the time they are next accessed, so no program will ever see an instance which does not contain the slot.

When new slots are added in this way, the initial value of the slot will be taken from the init-value or init-function. The init-keyword cannot be used, because there is no way for a program to supply the corresponding value. If there is no init-value or init-function, the slot remains uninitialized.

Supporting Virtual Slots

When you define a slot, Dylan ensures that there are generic functions defined in the module variables named by the setter and getter. If the allocation is not virtual, Dylan adds methods for accessing the slot to the getter and setter generic functions. For virtual slots, the programmer should explicitly define methods for the setter and getter generic functions.

The values of virtual slots are not automatically initialized when a new instance is created. The programmer must perform any necessary initialization. This would usually be done inside a method on initialize. Because the values of virtual slots are often computed from other values at run-time, many virtual slots will not require any explicit initialization.

The default version of slot-initialized? always returns true for virtual slots. To support the slot-initialized? protocol in a virtual slot, programmers must define a method for slot-initialized? that shares a protocol with the getter method for the slot.

Filtered Slots

In some situations, the value of a slot will be stored directly in instances of one class but will require some computation in a subclass. For example, the position slot could be stored as a value in direct instances of <view> while requiring some computation in direct instances of the <view> subclass <displaced-view>.

In this case, the <view> class supports position as an instance slot, and the <displaced-view> class supports position as a filtered slot. Methods for position and (setter position) are added automatically by the slot definition in <view> these methods access the raw value of the slot in the instance. In contrast, the implementor of <displaced-view> must explicitly add methods to the two generic functions. The <displaced-view> methods can call next-method to access (get or set) the stored value of the slot.

For the purposes of documentation, the implementor could declare position to be a virtual slot in <displaced-view>.

(define-class <view> (<object>)
  (position allocation: instance)
  ...)

(define-class <displaced-view> (<view>)
  (position allocation: virtual)
  ...)

(define-method position ((v <displaced-view>))
  (displace-transform (next-method v)))

(define-method (setter position) ((v <displaced-view>)
                                  new-position)
  (next-method v (undisplace-transform new-position)))
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>)
  (image allocation: virtual)
  (cached-image allocation: instance init-value: #f)
  ...)

(define-method image ((shape <shape>))
  (or (cached-image shape)
      (set! (cached-image shape) (compute-image shape))))

(define-method (setter image) ((shape <shape>) new-image)
  (set! (cached-image shape) new-image))
Redefining Slots

A subclass can redefine a slot inherited from a superclass. Similarly, if a class inherits the same slot from two or more superclasses, the inherited definitions must be resolved in some way.

Dylan considers two slots to be the same slot if they are read with the same getter generic function. The two slots are considered to fulfill the same protocol and hence are the same slot.

If you redefine any inherited slots when you create a new class, you must respecify all the slot features that were specified in the superior slot definition. If the superior version of the slot specifies a type, then the new definition of the slot must specify a type. If it specifies an init-value or init-function, then the new definition must specify an init-value or init-function. The init-keyword must also be specified if it was specified in the old definition. The allocation must be specified if it was anything besides instance in the old definition. If the old definition specified a setter (either directly, or as a default derived from the slot-name), then the new definition must specify a setter (directly, or through a slot-name).

The type specified for the new slot must be within the intersection of the types of all the slots being overridden (there may be more than one, if multiple inheritance is used).

If you inherit from multiple superclasses that specify the same slot, then there are two possibilities. If the superclasses have exactly the same definition for the slot, then you can simply inherit that definition. If the definitions differ, then you have to specify the slot in your class.

Defining individual slots

define-slot[return to first citation]   slot-owner [slot-name] keyword1 value1 keyword2 value2 ... 	[Macro]
==>  slot-name
define-slot defines a slot and adds it to slot-owner, which must be a class or a singleton. If slot-owner is a class, all instances of the class will be given a new slot. If it is a singleton, only the one object will be given a new slot.

The slot-name and keyword/value pairs are the same as for slot-specs given in define- class forms. Either the slot-name or the getter keyword must be specified; slot-name may be omitted if a getter is provided.

define-slot is normally called only to add slots to individual objects (i.e., slot-owner will be a singleton). Slots defined for classes are usually defined as part of the define-class form. If slot-owner is a singleton, then allocation: must be instance, constant, or virtual. Furthermore, neither init-keyword: nor required-init-keyword: may be specified.

Next chapter: Assignment