$16.1 INHERITANCE AND ASSERTIONS 579 invert (epsilon:REAL)is --Inverse of current matrix,with precision epsilon require epsilon>=10(-6) 。++ ensure ((Current inverse)-One)<=epsilon the redefined version may not use require and ensure but will appear as require else epsilon>=10^(-20) ensure then ((Current inverse)One)<=(epsilon 2) so that formally the precondition is (epsilon>=10(-20))or else (epsilon>=10(-6)), and similarly for the postcondition.But this does not really matter,since a weaker precondition or a stronger postcondition takes over:if a impliesy,then o or else y has the same value as y,and ifδimpliesβ,thenβand thenδhas the same value asδ.So mathematically the precondition of the redefined version is epsilon >=10^(-20)and its postcondition is ((Current inverse)One)<=(epsilon 2),even though the software assertions (and probably,in the absence of a symbolic expression simplifier,their evaluation at run time if assertion checking is enabled)are more complicated. Redeclaring into attributes See "Redeclaring a The Assertion Redeclaration rule needs a small complement because of the possibility of function into an redeclaring a function into an attribute.What happens to the original's precondition and attribute"page 491. postcondition,if any? An attribute is always accessible,and so may be considered to have precondition True.This means that we may consider the precondition to have been weakened,in line with the Assertion Redeclaration rule. An attribute,however,does not have a postcondition.Since it is necessary to guarantee that the attribute satisfy any property ensured by the original function,the proper convention(an addition to the Assertion Redeclaration rule)is to consider that the postcondition is automatically added to the class invariant.The flat form of the class will include the condition in its invariant. When expressing a property of the value of a function without arguments,you always have the choice between including it in the postcondition or in the invariant.As a matter of style it is considered preferable to use the invariant.If you follow this rule there will not be any change of assertions if you later redeclare the function as an attribute
§16.1 INHERITANCE AND ASSERTIONS 579 invert (epsilon: REAL) is -- Inverse of current matrix, with precision epsilon require epsilon >= 10 ^ (–6) … ensure ((Current ∗ inverse) |–| One) <= epsilon the redefined version may not use require and ensure but will appear as … require else epsilon >= 10 ^ (–20) … ensure then ((Current ∗ inverse) |–| One) <= (epsilon / 2) so that formally the precondition is (epsilon >= 10 ^ (–20)) or else (epsilon >= 10 ^ (–6)), and similarly for the postcondition. But this does not really matter, since a weaker precondition or a stronger postcondition takes over: if α implies γ, then α or else γ has the same value as γ; and if δ implies β, then β and then δ has the same value as δ. So mathematically the precondition of the redefined version is epsilon >= 10 ^ (–20) and its postcondition is ((Current ∗ inverse) |–| One) <= (epsilon / 2), even though the software assertions (and probably, in the absence of a symbolic expression simplifier, their evaluation at run time if assertion checking is enabled) are more complicated. Redeclaring into attributes The Assertion Redeclaration rule needs a small complement because of the possibility of redeclaring a function into an attribute. What happens to the original’s precondition and postcondition, if any? An attribute is always accessible, and so may be considered to have precondition True. This means that we may consider the precondition to have been weakened, in line with the Assertion Redeclaration rule. An attribute, however, does not have a postcondition. Since it is necessary to guarantee that the attribute satisfy any property ensured by the original function, the proper convention (an addition to the Assertion Redeclaration rule) is to consider that the postcondition is automatically added to the class invariant. The flat form of the class will include the condition in its invariant. When expressing a property of the value of a function without arguments, you always have the choice between including it in the postcondition or in the invariant. As a matter of style it is considered preferable to use the invariant. If you follow this rule there will not be any change of assertions if you later redeclare the function as an attribute. See “Redeclaring a function into an attribute”, page 491
580 INHERITANCE TECHNIQUES $16.2 A mathematical note An informal comment on the Assertion Redeclaration rule stated:"A redeclaration may Page 573. specialize the range of acceptable behaviors,but not violate it".Here,to conclude this discussion,is a rigorous form of that property (for mathematically inclined readers only). Consider that a routine implements a partial function r from the set of possible input states to the set of possible output states O.The routine's assertions define rules as to what r and its possible redeclarations may and may not do: The precondition specifies the domain DOM ofr(the subset of in which r is guaranteed to yield a result). The postcondition specifies,for each element x of DoM,a subset RESULTS (x)ofO such that r(x)E RESULTS (x).This subset may have more than one element,since a postcondition does not have to define the result uniquely. The Assertion Redeclaration rule means that a redeclaration may broaden the domain and restrict the result sets;writing the new sets in primed form,the rule requires that DOM'2 DOM RESULTS'(x)C RESULTS (x)for any x in DOM A routine's precondition specifies that the routine and its eventual redeclarations must at least accept certain inputs(DOM),although redeclarations may accept more.The postcondition specifies that the outputs produced by the routine and its eventual redeclarations may at most include certain values(RESULTS (x)),although redeclarations' postconditions may include fewer. In this description a state of a system's execution is defined by the contents of all reachable objects;in addition,input states (elements of /)also include the values of the arguments.For a more detailed introduction to the mathematical description of programs and programming languages see [M 1990]. 16.2 THE GLOBAL INHERITANCE STRUCTURE A few references have been made in earlier discussions to the universal classes GENERAL and ANY and to the objectless class NONE.It is time to clarify their role and present the global inheritance structure. Universal classes It is convenient to use the following convention. Universal Class rule Any class that does not include an inheritance clause is considered to include an implicit clause of the form inherit ANY referring to a Kernel library class ANY
580 INHERITANCE TECHNIQUES §16.2 A mathematical note An informal comment on the Assertion Redeclaration rule stated: “A redeclaration may specialize the range of acceptable behaviors, but not violate it”. Here, to conclude this discussion, is a rigorous form of that property (for mathematically inclined readers only). Consider that a routine implements a partial function r from the set of possible input states I to the set of possible output states O. The routine’s assertions define rules as to what r and its possible redeclarations may and may not do: • The precondition specifies the domain DOM of r (the subset of I in which r is guaranteed to yield a result). • The postcondition specifies, for each element x of DOM, a subset RESULTS (x) of O such that r (x) ∈ RESULTS (x). This subset may have more than one element, since a postcondition does not have to define the result uniquely. The Assertion Redeclaration rule means that a redeclaration may broaden the domain and restrict the result sets; writing the new sets in primed form, the rule requires that DOM' ⊇ DOM RESULTS' (x) ⊆ RESULTS (x) for any x in DOM A routine’s precondition specifies that the routine and its eventual redeclarations must at least accept certain inputs (DOM), although redeclarations may accept more. The postcondition specifies that the outputs produced by the routine and its eventual redeclarations may at most include certain values (RESULTS (x)), although redeclarations’ postconditions may include fewer. In this description a state of a system’s execution is defined by the contents of all reachable objects; in addition, input states (elements of I) also include the values of the arguments. For a more detailed introduction to the mathematical description of programs and programming languages see [M 1990]. 16.2 THE GLOBAL INHERITANCE STRUCTURE A few references have been made in earlier discussions to the universal classes GENERAL and ANY and to the objectless class NONE. It is time to clarify their role and present the global inheritance structure. Universal classes It is convenient to use the following convention. Universal Class rule Any class that does not include an inheritance clause is considered to include an implicit clause of the form inherit ANY referring to a Kernel library class ANY. Page 573
$16.2 THE GLOBAL INHERITANCE STRUCTURE 581 This makes it possible to define a certain number of features that will be inherited by all classes.These features provide operations of universal interest:copy,clone, comparison,basic input and output. For more flexibility,we will not put these features in ANY but in a class GENERAL of which ANy itself is an heir.ANY,in its default form,will have no features(being simply of the form class ANY inherit GENERAL end);but then a project leader or corporate reuse manager who wants to make a certain number of features available across the board can adapt ANY for local purposes without touching GENERAL,which should be the same in Versailles,Vanuatu,Venice and Veracruz. To build a non-trivial ANY,you may want to use inheritance.You can indeed make ANY inherit from some class HOUSE STYLE,or several such classes,without introducing any cycles in the inheritance hierarchy or violating the universal class rule:just make HOUSE_STYLE and its consorts explicit heirs of GENERAL.In the following figure,"All developer-written classes"means more precisely:all developer-written classes that do not explicitly inherit from GENERAL. Here then is a picture of the general structure: The global inheritance GENERAL structure ANY ..All developer-written classes... NONE
§16.2 THE GLOBAL INHERITANCE STRUCTURE 581 This makes it possible to define a certain number of features that will be inherited by all classes. These features provide operations of universal interest: copy, clone, comparison, basic input and output. For more flexibility, we will not put these features in ANY but in a class GENERAL of which ANY itself is an heir. ANY, in its default form, will have no features (being simply of the form class ANY inherit GENERAL end); but then a project leader or corporate reuse manager who wants to make a certain number of features available across the board can adapt ANY for local purposes without touching GENERAL, which should be the same in Versailles, Vanuatu, Venice and Veracruz. To build a non-trivial ANY, you may want to use inheritance. You can indeed make ANY inherit from some class HOUSE_STYLE, or several such classes, without introducing any cycles in the inheritance hierarchy or violating the universal class rule: just make HOUSE_STYLE and its consorts explicit heirs of GENERAL. In the following figure, “All developer-written classes” means more precisely: all developer-written classes that do not explicitly inherit from GENERAL. Here then is a picture of the general structure: The global inheritance structure GENERAL ANY NONE … All developer-written classes…
582 INHERITANCE TECHNIQUES $16.2 The bottom of the pit Also included in the figure is a class NONE,the nemesis of ANY:it inherits from any class that does not have any other heir and makes the global inheritance class a lattice.You probably do not want to see the rename subclauses of NONE and,be relieved,you will not.(It changes anyway each time someone writes a new class.)NONE is just a convenient fiction.But its theoretical existence serves two practical purposes: The type of Void,the void reference used among other things to terminate linked structures,is by convention NONE.(Void is in fact one of the features of GENERAL.) To hide a feature from all clients,export it to NONE only (in a feature clause of the form feature (NONE),equivalent in practice to feature)but more explicit,or in an inheritance subclause export NONE),also with the same practical effect as export).This will make it unavailable to any developer class,since NONE has no proper descendants.Note that NONE hides all its features. On the first property,note that you may assign the value loid to an entity of any reference type;so until now the status of loid was a little mysterious,since it had somehow to be compatible to all types.Making NONE the type of loid makes this status clear,official,and consistent with the type system:by construction,NONE is a descendant of all classes,so that we can use loid as a valid value of any reference type without any need to tamper with the type rules. On the second property note that,symmetrically,a feature clause beginning with just feature,which exports its features to all developer classes,is considered a shorthand for feature (ANY).To reexport to all classes a parent feature which had tighter availability, you may use export ANy),or the less explicit shorthand export. ANY and NONE ensure that our type system is closed and our inheritance structure complete:the lattice has a top and it has a bottom. Universal features Here is a small sampling of the features found in GENERAL and hence available to all classes.Several of them were introduced and used in earlier chapters: clone for duplicating an object,and its deep variant deep clone for recursively See "Object cloning duplicating an entire object structure. and equality”,page 245,and subsequent copy for copying the contents of an object into another. sections. equal for field-by-field object comparison,and its deep variant deep equal. Other features include: print and print line to print a simple default representation of any object. tagged out,a string containing a default representation of any object,each field accompanied by its tag (the corresponding attribute name). same type and conforms to,boolean functions that compare the type of the current object to the type of another
582 INHERITANCE TECHNIQUES §16.2 The bottom of the pit Also included in the figure is a class NONE, the nemesis of ANY: it inherits from any class that does not have any other heir and makes the global inheritance class a lattice. You probably do not want to see the rename subclauses of NONE and, be relieved, you will not. (It changes anyway each time someone writes a new class.) NONE is just a convenient fiction. But its theoretical existence serves two practical purposes: • The type of Void, the void reference used among other things to terminate linked structures, is by convention NONE. (Void is in fact one of the features of GENERAL.) • To hide a feature from all clients, export it to NONE only (in a feature clause of the form feature {NONE}, equivalent in practice to feature { } but more explicit, or in an inheritance subclause export {NONE}, also with the same practical effect as export { }). This will make it unavailable to any developer class, since NONE has no proper descendants. Note that NONE hides all its features. On the first property, note that you may assign the value Void to an entity of any reference type; so until now the status of Void was a little mysterious, since it had somehow to be compatible to all types. Making NONE the type of Void makes this status clear, official, and consistent with the type system: by construction, NONE is a descendant of all classes, so that we can use Void as a valid value of any reference type without any need to tamper with the type rules. On the second property note that, symmetrically, a feature clause beginning with just feature, which exports its features to all developer classes, is considered a shorthand for feature {ANY}. To reexport to all classes a parent feature which had tighter availability, you may use export {ANY}, or the less explicit shorthand export. ANY and NONE ensure that our type system is closed and our inheritance structure complete: the lattice has a top and it has a bottom. Universal features Here is a small sampling of the features found in GENERAL and hence available to all classes. Several of them were introduced and used in earlier chapters: • clone for duplicating an object, and its deep variant deep_clone for recursively duplicating an entire object structure. • copy for copying the contents of an object into another. • equal for field-by-field object comparison, and its deep variant deep_equal. Other features include: • print and print_line to print a simple default representation of any object. • tagged_out, a string containing a default representation of any object, each field accompanied by its tag (the corresponding attribute name). • same_type and conforms_to, boolean functions that compare the type of the current object to the type of another. See “Object cloning and equality”, page 245, and subsequent sections
$16.3 FROZEN FEATURES 583 generator,which yields the name of an object's generating class-the class of which it is a direct instance. 16.3 FROZEN FEATURES The presentation of inheritance has repeatedly emphasized the Open-Closed principle:the ability to take any feature from an ancestor class and redefine it to do it something different. Can there be any reason for shutting off this possibility? Prohibiting redefinition The discussion of assertions at the beginning of this chapter has provided us with the theoretical understanding of redefinition:the "open"part of the Open-Closed principle- the ability to change features in descendants-is kept in check by the original assertions The only permitted redefinitions change the implementation while remaining consistent with the specification given by the precondition and postcondition of the original In some rare cases,you may want to guarantee to your clients,and to the clients of your descendants,not only that a feature will satisfy the official specification,but also that it will use the exact original implementation.The only way to achieve this goal is to forbid redeclarations altogether.A simple language construct provides this possibility: frozen feature name...is...The rest of the feature declaration as usual .. With this declaration,no descendant's redefine or undefine subclause may list the feature,whether under its original name or(since renaming remains of course permitted) another.A deferred feature-meant,by definition,for redeclaration-may not be frozen. Fixed semantics for copy,clone and equality features The most common use of frozen features is for general-purpose operations of the kind just reviewed for GENERAL.For example there are two versions of the basic copy procedure: copy,frozen standard copy (other:...is --Copy fields of other onto fields of current object. require other not void:other /Void do ensure equal (Current,other) end This declares two features as synonyms.(A general convention allows us to declare two features together so that they can share the same declaration;just separate their names with commas as here.The effect is as if there had been two separate declarations with identical declaration bodies.)But only one of the features is redefinable.So a descendant class can redefine copy;this is necessary for example for classes ARRAY and STRING
§16.3 FROZEN FEATURES 583 • generator, which yields the name of an object’s generating class — the class of which it is a direct instance. 16.3 FROZEN FEATURES The presentation of inheritance has repeatedly emphasized the Open-Closed principle: the ability to take any feature from an ancestor class and redefine it to do it something different. Can there be any reason for shutting off this possibility? Prohibiting redefinition The discussion of assertions at the beginning of this chapter has provided us with the theoretical understanding of redefinition: the “open” part of the Open-Closed principle — the ability to change features in descendants — is kept in check by the original assertions. The only permitted redefinitions change the implementation while remaining consistent with the specification given by the precondition and postcondition of the original. In some rare cases, you may want to guarantee to your clients, and to the clients of your descendants, not only that a feature will satisfy the official specification, but also that it will use the exact original implementation. The only way to achieve this goal is to forbid redeclarations altogether. A simple language construct provides this possibility: frozen feature_name … is … The rest of the feature declaration as usual … With this declaration, no descendant’s redefine or undefine subclause may list the feature, whether under its original name or (since renaming remains of course permitted) another. A deferred feature — meant, by definition, for redeclaration — may not be frozen. Fixed semantics for copy, clone and equality features The most common use of frozen features is for general-purpose operations of the kind just reviewed for GENERAL. For example there are two versions of the basic copy procedure: copy, frozen standard_copy (other: …) is -- Copy fields of other onto fields of current object. require other_not_void: other /= Void do … ensure equal (Current, other) end This declares two features as synonyms. (A general convention allows us to declare two features together so that they can share the same declaration; just separate their names with commas as here. The effect is as if there had been two separate declarations with identical declaration bodies.) But only one of the features is redefinable. So a descendant class can redefine copy; this is necessary for example for classes ARRAY and STRING