Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!usc!ucsd!hub.ucsb.edu!eiffel!bertrand From: bertrand@eiffel.UUCP (Bertrand Meyer) Newsgroups: comp.object Subject: Re: OO Ada -- another way Message-ID: <445@eiffel.UUCP> Date: 8 Nov 90 21:31:05 GMT References: <77500062@m.cs.uiuc.edu> <443@eiffel.UUCP> <1990Nov7.220902.13393@Neon.Stanford.EDU> Organization: Interactive Software Engineering, Santa Barbara CA Lines: 159 From <1990Nov7.220902.13393@Neon.Stanford.EDU> by craig@Neon.Stanford.EDU (Craig D. Chambers): > > I disagree that Eiffel's typing rules "follow directly" from treating > a class as both a module (a collection of method implementations) and > a type (a set of procedure/function interfaces). > > [Various incorrect statements.] > I don't really see the benefit of attributing to someone (me in this case) comments which are the exact reverse of what that person actually wrote. This defeats the whole purpose of any technical discussion - ever more regrettably that other aspects of Mr. Chambers's posting are worth discussing. So I'll just pass, referring any interested party to existing publications and previous extensive postings on comp.lang.eiffel and other places. > > (c) Reverse Assignment Attempt > > What does this mean? Think of Reverse Assignment Attempt as disciplined, controlled casting. The instruction y ?= x is valid only if the type of y conforms to the type of x. (``Conforms to'' roughly means ``is a descendant of'' in the sense of inheritance, with some extra care due to genericity and expanded types.) For normal assignment (x := y), conformance must be the other way. A Reverse Assignment Attempt such as the above, which assumes that x and y are references, has the effect of attaching to y the object attached to x (as any assignment should do), but only if x is attached to an object whose type conforms to the type of y. In all other cases (i.e. x is attached to an object of non-conformant type, or x is void, that is to say not attached to any object), the effect is to make y void. In a typed environment this is needed for situations when the programmer has (or thinks he has) the type information associated with certain object, but the software text does not have it (often because it has lost it). This happens particularly when dealing with persistent data: when retrieving an object structure (stored e.g. using the STORABLE class, which maps an entire data structure, with all references preserved, cycles and all, onto permanent storage), you will use general-purpose mechanisms, which can only assume the most general type (ANY). If you know, or think you know, the actual type of a retrieved object, you need the Reverse Assignment Attempt to manipulate that object under its proper type - after checking that your assumptions are indeed correct. The scheme is: value: ANY; -- ANY is the most general type, -- to which all types conform my_value: ASSUMED_TYPE; ... value := retrieve (data_base, key, ...) -- `retrieve' is a general-purpose mechanism, -- which can only assume ANY as type information -- for its result. my_value ?= value; if my_value.Void then -- My assumption was wrong! No object -- was retrieved from the database, -- or an object of the wrong type "Some error or special processing" else -- Proceed normally with ASSUMED_TYPE operations -- on the retrieved object: my_value.op1; my_value.op2; ... end - > The OrderedCollection problem could be avoided if > the language includes a default instance of each class (such as a null > pointer), but null pointers (void references in Eiffel) just introduce > a whole new class of run-time errors that cannot be detected > statically (e.g. sending messages to null pointers). This is correct, but unfortunately there is no way of dealing with these errors statically in a typed language that supports references. (At least there is no practical way unless one is prepared to include a general-purpose resolution theorem prover in the compiler.) Giving up references means giving up linked structures, cyclic ones etc. This does not seem realistic. If we do have references, then we need a void reference. Then the possibility of a program attempting to access a non-existent object (through a void reference) at run-time exists, and cannot be detected statically in the general case. Look for example at code of the following form (it is written in Eiffel, but it just as well be Pascal or Ada): m, n: INTEGER; x: SOME_REFERENCE_TYPE; ... m := prime (8); -- Function prime (i), part of the same class, returns -- the i-th prime, with 1 being the first prime. Here -- the result will be the eigth prime number, 17 n := prime (9); -- Result will be 19 if n - m = 2 * (n - m) then x.Create -- Here x will be non-void. If the test -- yielded false x would be void. end; x.some_operation_of_class_SOME_REFERENCE_TYPE There is simply no reasonable way (now or in the foreseeable future) to determine statically, as part of a compiling process, that x will indeed be non-void at the type of the call. This would require that the compiler be able to analyze the text of function `prime' (which is assumed to be a programmer-supplied function, written as part of the same class) and to prove that the difference between the values computed by that function for arguments 8 and 9 is twice n - m, that is to say 2. One could argue for restrictions on the use of references which would disallow code such as the above. But it is not clear what kinds of restriction are acceptable (i.e. would make static checking possible without prohibiting useful cases.) At best, we can hope to replace full-fledged program proving by data flow analysis, which is easier but not much more realistic. The approach taken by the designers of all languages supporting references or pointers is that the vacuity question (whether or not an entity will be void before application of an operation) has to be resolved at run time. As a consequence vacuity is NOT a typing property in Pascal, Ada etc. In Eiffel it was felt intellectually more satisfying to cast this property in typing terms, but the end result is the same. In fact the Eiffel approach may be characterized as follows: all type checking may be done statically with ONE exception - the vacuity test. This explains the rationale for the Reverse Assignment Attempt as described above: in some cases, such as the retrieval of persistently stored data, a type check must perforce be done dynamically since we are accessing data over which we have no control, and need to ``clear'' it, in the sense of security clearance, before we start acting on it as bona fide object-oriented data. We do not, however, want these (important but infrequent) cases to make static type checking impossible in all other ``normal'' cases. Then the solution provided by Reverse Assignment Attempt is to replace a type check (which should always be static) by the only dynamically checked property: vacuity. -- -- Bertrand Meyer bertrand@eiffel.com