Chapter 5

Types and Classes

Classes

Classes are used to define the inheritance, structure, and initialization of objects.

Every object is a direct instance of exactly one class, and a general instance of the general superclasses of that 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.

Classes determine how their instances are initialized by using the initialization protocol.

Features of Classes

There are four features of classes. These features relate to each other, but can be declared independently.

Creating Classes

New classes may be created by calling make on <class>, or with the definition define class. In most programs the latter is more commonly used.

When a class is created with make, it is instantiated and returned just like any other object. The options available when creating a class with make are described on page 191.

When a class is created with define class it is used to initialize a new module binding. define class allows the specification of superclasses, slots, initialization behavior, and options related to sealing. The complete syntax of define class is given on page 378.

The following simple class definition creates a class named by the module binding <new>. The class inherits from <object> and does not specify any slots.

define class <new> (<object>)
end class <new>;

The following class definition illustrates the creation of a class with multiple superclasses. Again, there are no slots defined by the class.

define class <color-window> (<palette>, <window>)
end class <color-window>;

Class Inheritance

When a class is created, its direct superclasses are specified. The new class directly inherits from these classes; it is a direct subclass of each of these classes. There can be no duplicates in the direct superclasses of a class.

The subclass relationship is transitive. If a class C is a direct subclass of C1, C1 is a direct subclass of C2, and C2 is a direct subclass of C3, then C is an indirect subclass of C2 and C3. A general subclass is a direct or indirect subclass.

Inheritance cannot be circular. A class cannot be its own general subclass.

A class is a subtype of each of its general superclasses.

Every class is a general subclass of <object>.

Computing the Class Precedence List

The definition of a class specifies a total ordering on that class and its direct superclasses. This ordering is called the local precedence order. In the local precedence order:

The class precedence list for a class C is a total ordering on C and its superclasses that is consistent with the local precedence order of C and with the class precedence lists of its superclasses. (Two lists are consistent if for every A and B that are each members of both lists, either A precedes B in both or B precedes A in both.) The class precedence list is used in determining the order of specificity of methods based on the types they are specialized on when dispatching; for details, see Method Dispatch on page 95.

Sometimes there are several such consistent total orderings on C and its superclasses. Dylan uses a deterministic algorithm to compute the class precedence list, which chooses one of the consistent total orderings.

Sometimes there is no possible total ordering on C and its superclasses that is consistent with the local precedence orders for C and with the class precedence lists of its superclasses. In this case, the class precedence list cannot be computed, and an error is signaled.

Note that because the class precedence list for a class is consistent with the class precedence lists of its superclasses, inheritance in Dylan is monotonic. That is, if a generic function call using a direct instance of C dispatches to a method specialized in that parameter position on an indirect superclass of C, then there is a direct superclass of C that has the same behavior.

To compute the class precedence list for class C, merge the local precedence order of the class with the class precedence lists of the direct superclasses of the class. Computing the class precedence list for C requires computing the class precedence lists for its superclasses. This does not lead to infinite recursion because circular class inheritance is prohibited.

Note that because the class precedence lists of the direct superclasses are consistent with their local precedence orders and with the class precedence lists of their direct superclasses, and so on, the class precedence list for C is consistent with the local precedence orders and class precedence lists of all its superclasses and not just the direct superclasses.

The merge of several sequences is a sequence that contains each of the elements of the several input sequences. An element that appears in more than one of the input sequences appears only once in the output sequence. If two elements appear in the same input sequence, their order in the output sequence is the same as their order in that input sequence.

When there are several possible merges of the inputs, at each position in the output where there is a choice, pick the class that has a direct subclass closest to the end of the output sequence. (This is unambiguous because two candidates for a position cannot both be direct superclasses of the same class, since they would then be ordered by that class's local precedence order. This is easily computable because a class always follows its direct subclasses in the merge, therefore the most recently added direct subclass can be found by searching from the end to the beginning of the output sequence and the merge can be computed in one pass.)

This algorithm can be implemented with the following Dylan program:

define constant compute-class-linearization =
  method (c :: <class>) => (cpl :: <list>)
    local method merge-lists (reversed-partial-result :: <list>,
                              remaining-inputs :: <sequence>)
            if (every?(empty?, remaining-inputs))
              reverse!(reversed-partial-result)
            else
              local method candidate (c :: <class>)
                      // returns c if it can go in the result now,
                      // otherwise false
                      local method head? (l :: <list>)
                              c == head(l)
                            end method head?,
                            method tail? (l :: <list>)
                              member?(c, tail(l))
                            end method tail?;
                      any?(head?, remaining-inputs)
                        & ~any?(tail?, remaining-inputs)
                        & c
                    end method candidate,
                    method candidate-direct-superclass (c :: <class>)
                      any?(candidate, direct-superclasses(c))
                    end method candidate-direct-superclass;
              let next = any?(candidate-direct-superclass,
                              reversed-partial-result);
              if (next)
                local method remove-next (l :: <list>)
                        if (head(l) == next) tail(l) else l end
                      end method remove-next;
                merge-lists(pair(next, reversed-partial-result),
                            map(remove-next, remaining-inputs))
              else
                error("Inconsistent precedence graph");
              end if
            end if
          end method merge-lists;
    let c-direct-superclasses = direct-superclasses(c);
    local method cpl-list (c)
            as(<list>, all-superclasses(c))
          end method cpl-list;
    merge-lists(list(c),
                add(map(cpl-list, c-direct-superclasses),
                    as(<list>, c-direct-superclasses)));
  end method; // compute-class-linearization

Note that the selection rule from above is enforced because any? uses the natural iteration order for sequences and returns the first true value it encounters when searching the reversed partially computed class precedence list.