next prev up top content index

9 Sealing

Define Sealed Domain

define sealed domain is used to make specific portions of a generic function and of the class hierarchy invariant without disallowing all future changes. The arguments to define sealed domain are an explicitly known generic function and a series of types, one for each required argument of the generic function.

The complete syntax of define sealed domain is given on page 388.

A define sealed domain definition in a library L for a generic function G with types T1…Tn imposes the following constraints on programs:

  1. A method M that is congruent to G and that is not an explicitly known method in L may be added to G only if at least one of the specializers for M is disjoint from the corresponding T.
  2. A method M may be removed from G only if at least one of the specializers for M is disjoint from the corresponding T.
  3. A class C (with direct superclasses D1Dm) that is not explicitly known in L may be created only if no method in G actually blocks C.
    • A method M (with specializers S1Sn) in G potentially blocks C at argument position i if there exist j and k such that Dj is a pseudosubtype of Si, Dk is a pseudosubtype of Ti, and Dk is not a pseudosubtype of Si.
    • A method M actually blocks C if M potentially blocks C at some argument position, and for every argument position i where Si and Ti are disjoint, M potentially blocks C at i.

The third constraint is illustrated by the following example:

define generic m (x);
define class <t> (<object>) end class <t>;
define class <s> (<object>) end class <s>;
define method m (s :: <s>) end method m;
define sealed domain m (<t>);

define class <c> (<s>, <t>) end class <c>;

The definition of class <c> would be valid if it appeared in the same library as the preceding definitions or in a library used by them, but invalid if it appeared in a different library. The reason is that without the definition of <c>, the method defined on m is not within the domain declared by the define sealed domain, but with the definition of <c> the method is within that domain.

Rationale

define sealed domain permits the compiler to assume certain properties of the program that can be computed based on explicitly known classes and methods, with a guarantee that an attempt to violate any of those assumptions will be detected.

The goal of rule 3 is that the creation of the class C must not make any method M applicable to a part of the sealed domain to which it was not previously applicable.

The "potentially blocks" concept describes the mechanism for testing whether the set of objects that are instances of both Si and Ti (i.e., to which the method is applicable at the ith argument position and that are within the sealed domain at that argument position) would change as a result of creating C. By specifying what valid programs are allowed to do, rule 3 implicitly specifies the assumptions a compiler can make. A define sealed domain definition accomplishes this by permitting the compiler to eliminate some of the known methods on a generic function from the set of methods that might be applicable to a particular call at runtime. For example, if this leaves exactly one applicable method, the compiler can eliminate a run-time method dispatch and consider additional optimizations such as inlining.

Specifically, suppose the compiler is compiling a call to G and has determined that the argument at position i is an instance of some type U (where U is not necessarily a standard Dylan type, but could instead be a compiler-internal extension to the type system, such as a difference of two types). For the compiler to be able to rely on the define sealed domain definition, U must be a subtype of Ti. For the compiler to determine that M is not applicable, U must be disjoint with Si. Creating C can't change whether U is a subtype of Ti, but it can change whether U is disjoint with Si. If there could be an object that is simultaneously an instance of U, C, and Si, it would violate the compiler's assumption that M is not applicable in the call to G, and therefore creating C would be a sealing violation. If there can't be such an object, then creating C is allowed.

This maps onto rule 3 as follows (ignoring for the moment the added complication of limited types that lead to the use of the pseudosubtype relationship rather than subtype):

U is a subtype of Dk and therefore is a subtype of Ti, because subtype is transitive.

Dk is not a subtype of Si, because if it were then U could not be disjoint from Si.

Dj is a subtype of Si.

If U and C would have a nonempty intersection, then the creation of C must be prevented, else U would no longer be disjoint from Si. One possible U is the set of all general instances of Dk that are not also general instances of any of the explicitly known direct subclasses of Dk. That U would indeed have a non-empty intersection with C. The existence of this U makes the proposed rule 3 necessary.

Rule 3 does not need to address the possibility of multiple inheritance being used to combine classes involved in the element types of limited collection classes. Changes to the disjointness relationships between element types does not affect the relationships between collection types with those element types.

Pseudosubtype Examples

Suppose A and B are disjoint subclasses of <collection>, Si is limited(A, of: T), and Ti is limited(B, of: T). Thus, Si and Ti are disjoint and M is outside the sealed domain. If C inherits from A and B it should be potentially blocked by M, because an instance of limited(C, of: T) would be an instance of both Si and Ti. Since B is not a subtype of Ti, there would be no blockage if the constraints in rule 3 were defined in terms of subtype. However, B is a pseudosubtype of Ti, so specifying rule 3 using the pseudosubtype relationship correctly causes M to potentially block C.

Suppose Si is limited(<stretchy-vector>, of: <integer>) and Ti is limited(<sequence>, of: <integer>). It should be possible to create <stretchy-string>, a direct subclass of <stretchy-vector> and <string>. The element-type of <stretchy-string> must be a subtype of <character>, therefore, assuming <integer> and <character> are disjoint, <stretchy-string> is disjoint from both Si and Ti, and so is not blocked. This example shows the need for the non-disjointness requirement in the definition of pseudosubtype.

Abbreviations for Define Sealed Domain

define sealed method defines a method on a generic function and also seals the generic function for the types that are the specializers of the method.

The following two program fragments are equivalent:

define sealed method insert (source :: <list>, i :: <object>)
  => (result :: <list>)
  …
end method insert;

and

define method insert (source :: <list>, i :: <object>)
  => (result :: <list>)
  …
end method insert;
define sealed domain insert (<list>, <object>);

The sealed slot option to define class defines a slot and also makes the getter generic function sealed over the class, and the setter generic function, if there is one, sealed over the type of the slot and the class.

The following two program fragments are equivalent:

define class <polygon> (<shape>)
  sealed slot sides :: <integer>, required-init-keyword: sides:;
end class <polygon>;

and

define class <polygon> (<shape>)
  slot sides :: <integer>, required-init-keyword: sides:;
end class <polygon>;
define sealed domain sides (<polygon>);
define sealed domain sides-setter (<integer>, <polygon>);

Implied Restrictions on Method Definitions

To avoid potential sealing violations among separately developed libraries, one of the following conditions should be true for every method M defined in a library L:

The following example illustrates why this condition is necessary.

Library L1 defines and exports the following:

define generic g (x)
define class <c1> (<object>) end class <c1>;

Library L2 uses L1 and defines the following

define class <c2> (<c1>) end class <c2>;
define method g (x :: <c2>) end method;
define sealed domain g (<c2>)

Library L3 uses L1 and defines the following

define method g (x :: <c>) end method;

Libraries L2 and L3 are developed independently, and have no knowledge of each other. An application that attempts to use both L2 and L3 contains a sealing violation. L2 is clearly valid. Therefore, L3 is at fault for the sealing violation. Because the compiler cannot prove that use of L3 will lead to an error (and indeed, it will only lead to an error in the presence of L2), it is appropriate to issue a warning but not disallow the compilation of L3.