======= LSN012.RipEffect ======= !topic LSN on the "Ripple" Effect, Preference rules, etc. !from Tucker Taft 91-06-11 !discussion This LSN discusses some general underlying principles which seem to be inherent in the current list of special-case Ada-83 visibility and overload resolution rules. Hopefully by identifying these general principles, we may be able to express them directly rather than through special-case rules, thereby simplifying presentation, exposing more of the rationale to the RM reader, and making sure that our rules actually follow some sort of consistent model. We identify a new to-be-avoided effect (the Ripple Effect), analogous to the infamous Beaujolais effect, which seems to be the underlying problem justifying some of the existing special case overloading rules (e.g. may not apply an explicit conversion to "null"). We also discuss the various "implicit" operations which are permitted in Ada-83, and what special-case visibility and overload resolution rules apply to reduce ambiguity. Finally, we come around to discussing how these general principles relate to Ada 9X, and what approach might be taken to make these principles more explicit and consistently followed. THE RIPPLE EFFECT(S) In keeping with the Ada-83 "spirit," we have dubbed another to-be-avoided effect for Ada-9x the "Ripple" effect. Anyone who can find a not-previously reported instance of this in Ada 9X will be awarded a jug of Ripple wine (we may need to excavate a 1970's time capsule to find one). Just as Beaujolais is best when drunk young, Ripple is best when you're drunk and young. Choosing Ripple was felt to be appropriate as the venue of the standardization effort moved from France to the land of the fine Concord grape. Anyway... The following Ripple Effects are to be avoided: 1) The "really-bad" Ripple Effect: If by adding or removing a "with" clause from a package spec, but making no other change, the legality or meaning of some compilation unit outside the package, but that depends on the package, is affected. 2) The "pretty-bad" Ripple Effect: If by adding or removing a "with" clause from a package spec that makes no use of the name appearing in the with clause, the legality or meaning of the package itself is affected. For reference, here is the approximate definition of the Beaujolais Effect: 3) The Beaujolais Effect: If by adding or removing a "use" clause, or by adding or removing a single declaration from a "use"d package, the meaning of the compilation unit with the use clause changes from one legal interpretation to a different legal interpretation. RATIONALE The really-bad Ripple Effect is particularly troublesome, because it is a common maintenance activity to add or remove a with clause, perhaps as part of changing the full declaration of a private type, or because it is determined that the with clause could be moved to the body from the spec, thereby reducing the recompilation burden. Ada-83 is very explicit that the library unit name introduced by a with clause is not made visible to client compilation units. It should therefore be similarly safe to add or remove an unused with clause without affecting clients. Furthermore, if a package is provided in a precompiled state (such as Text_IO), then it is common when publishing the specification for such a package to omit irrelevant with clauses, particularly those which apply only to declarations in the private part. The pretty-bad Ripple Effect is less troublesome, since there are a very well-specified set of places to check for with clauses, and generally when compiling a body or a subunit, the source for the spec or ancestor units is readily available. However, it is still surprising if one decides to remove a with clause because there are no apparent remaining uses of a library unit (presumably determined by searching for uses of the library unit name within the unit), to suddenly discover that some construct becomes illegal. For example, when trying to remove an apparently redundant "with" of package System, one might find that an address clause with an integer literal as the address, would suddenly become illegal. However, System is special in other ways so one quickly learns that a "with" of System has special significance. ADA-83 SCORECARD Our analysis shows that, thanks to a number of special-case overload resolution rules (e.g. no explicit conversion of "null"), the Ada-83 RM managed to avoid the "really-bad" Ripple Effect. However, this welcome state of affairs was changed by a recent AI which requires that 'ADDRESS only be legal if the compilation unit *depends on* (directly or *indirectly*) package System. On the other hand, Ada-83 does have a few instances of the "pretty-bad" Ripple Effect. In particular, a with clause for package System must be applicable to a unit which contains an address clause, and a with clause for package Machine_Code must be applicable to a unit which contains a machine-code insertion. To remind the non-language-lawyers in the crowd the difference between "depends on (directly or indirectly)" and "a with clause is applicable" is that the former allows the with clause to be on some package which itself is "with"ed, whereas the latter requires the with clause to be on the compilation unit itself, its specification (if a library unit), or one of its ancestor units (if a subunit). Another way to say it is that if a with clause is "applicable," then the name for the library unit is visible, whereas if the unit is merely depended-on, the name of the library unit is not necessarily visible. The way to remove the recently introduced really-bad Ripple Effect w.r.t. 'ADDRESS, is to either have no special rule, since it is very rare anyway that a program could use 'ADDRESS without having System somewhere in scope, or have a special rule consistent with the special rule for address clauses, namely that a "with" for System must "apply" to the compilation unit. IMPLICIT OPERATIONS Another general principle relates to the preference against implicit operations. Ada-83 allows certain implicit operations to be performed without any syntax in the program specifying the operation. In particular, there is implicit conversion of a universal type, implicit dereference of an access value when used as a prefix (although it is not described this way in the RM, most Ada programmers informally think of it as implicit dereference), and implicit "deproceduring" (to borrow an Algol-68 term) of parameterless functions. In all cases there is a preference for not performing the implicit operation if possible. These preferences against implicit operations are expressed through a varied set of special-case rules: a) RM 4.6(15) prefers no implicit conversion; b) RM 13.7.2(6) prefers no implicit dereference nor deproceduring of the prefix to 'ADDRESS and 'SIZE; The preference against dereference is also implicit in the list of operations of an access type given in RM 3.8.2(4), though it is not highlighted. c) RM 4.1.3(19) prefers no deproceduring of the prefix in a selected component notation. It is worth considering whether these varied special-case rules could be reexpressed in a more general manner, and whether they need any adjustment to accommodate proposed Ada 9X features. Ada 9X AND GENERAL OVERLOADING/PREFERENCE PRINCIPLES It seems worthwhile to continue to avoid the Ripple Effects in Ada 9X, as well as maintain the general preference against implicit operations. At the same time, however, it would be ideal if the presentation of the overloading and preference rules could be improved to turn the current special cases into ramifications of some more general rule(s). Class-Wide Operations and the Ripple Effects One of the thrusts of the 9X OOP is to increase the applicability and flexibility of "class-wide" operations. User-defined operations on universal types, and enhanced visibility of primitive operators are two ways of extending operations across an entire class of types. However, as part of doing this there is a danger of introducing a really-bad Ripple Effect if the presence of an otherwise unused type declaration somewhere in scope can affect the interpretation of an expression. Therefore we have proposed rules which limit where primitive visibility and implicit conversions are considered. To be more specific, if we defined primitive operator visibility to be simply "consider all primitive operators in scope equally" in conjunction with "consider all possible implicit conversions, if an interpretation without implicit conversion doesn't exist" then we introduce a really-bad Ripple Effect, as follows: Assume there is a package far away... package Far_Away is type Far_Away_Type is new Integer; function "+"(Left: Integer; Right : Far_Away_Type) return Integer; . . . In the current compilation unit: Y : Integer; X : Integer := Y + 4; If the Far_Away_Type "+" is considered based strictly on the presence of "with" clauses, then we have either a really-bad or pretty-bad Ripple Effect if the "with" for this package is added or removed, depending on whether we require Far_Away to be depended-on, or to be named in an applicable with clause, respectively. [Robert's proposed "with-clause makes ops visible" corresponds to this second case.] However, we have proposed a different rule which avoids both Ripple Effects. The presence or absence of with clauses is not directly relevant, but instead, it is the presence of an operand of the type which matters (not considering implicit conversions or "class-wide overloading"). [By "class-wide overloading" we mean the overloading associated with constructs like "null," string literals, aggregates, and allocators, which are overloaded on all types in an open-ended set of types.] There are analogous rules in Ada-83 which disallow explicit conversion when applied to class-wide overloadings. These are coupled with rules which require that class-wide overloadings be resolvable using only result context and the general requirements for the class. POSSIBLE GENERAL OVERLOADING/PREFERENCE RULES Here is an attempt to formulate a general set of rules which handles the existing Ada-83 special cases, plus the proposed 9X primitive operator visibility and user-defined operations on universal types: In general, an operation may yield a result which is overloaded on an open-ended set of types, as well as possibly overloaded on one or more specific result types. An integer literal is an example of an operation that yields a result of the specific type universal integer, and is also effectively overloaded on the open-ended set of integer types via implicit conversion. An allocator is an example of an operation whose result is overloaded on the open-ended set of access types which designate a given type. In general, an operand of an operation may take a specific type, as well as possibly being overloaded on an open-ended set of types. Explicit type conversion is an example of an operation with an operand which is overloaded on an open-ended set of types, namely any type which is a "relative" of the target type. 'VAL is an example of an operation with an operand which is overloaded on the open-ended set of integer types. When a result or an operand is overloaded on an open-ended set of types, either directly or via implicit conversion, we call this "class-wide" result or operand overloading. So here are the general restrictions: 1) Class-wide result overloading may not be used to resolve class-wide operand overloading, nor vice-versa. 2) If an interpretation using class-wide result overloading is to be considered, then the result context must identify a single type which satisfies the requirements of the class. These two restrictions seem to cover all of the existing Ada-83 special cases having to do with literals, allocators, aggregates, explicit conversion, implicit conversion, etc, as well as the proposed 9X visibility rules having to do with universal types and primitive operators. Restriction (1) means in practice that there is never a need to iterate over all the types of an open-ended set to determine whether there is exactly one which satisfies the general requirements associated with class-wide overloading. Restriction (2) means that class-wide result overloading is only considered when there is no enclosing ambiguity. These restrictions together prevent the very useful concept of class-wide overloading from possibly introducing very troublesome Ripple Effects. The proposed rules for primitive visibility follow from these general restrictions by considering the result of using an operator to be overloaded on the specific result types identified by looking only at the operand interpretations, plus on the open-ended set of all types. ================================= We welcome very close scrutiny of the proposed general restrictions related to class-wide overloading. If they can in fact capture most or all of the existing Ada-83 and Ada-9X special case rules for overloading then they may help simplify the reference manual presentation, as well as provide a more solid underlying model for resolving overloading. -Tuck