Previous | Table of Contents | Next |
The type conformance rule (section 9.9.3) ensures type safety by requiring all assignments to be from a more specific source to a more general target.
In some cases, the type of the target object cannot be known for sure. This happens, for example, when the target comes from the outsidea file, a database, or a network. The persistence storage mechanism studied in section 9.6.11 includes, along with the procedure store seen there, the reverse operation, a function retrieved, which yields an object structure retrieved from a file or network, to which it was sent using store. But retrieved as declared in the corresponding class STORABLE of EiffelBase can only return the most general type, ANY; the exact type can only be ascertained at execution time because the corresponding objects are not under the control of the retrieving system and might even have been corrupted by some external agent.
In such cases, we cannot trust the declared type, but must check it against the type of an actual runtime object. Eiffel introduces for this purpose the assignment attempt operation, written
x ?= y
with the following effect (only applicable if x is a writable entity of reference type):
Using this mechanism, a typical object structure retrieval is of the form
x ?= retrieved if x = Void then We did not get what we expected else Proceed with normal computation, which will typically involve calls of the form x.some_ feature end
As another application, assume we have a LIST [ACCOUNT], and class SAVINGS_ACCOUNT, a descendant of ACCOUNT, has a feature interest_rate that was not in ACCOUNT. We want to find the maximum interest rate for savings accounts in the list. Assignment attempt easily solves the problem:
local s: SAVINGS_ACCOUNT do from account_list.start until account_list.after loop s ?= acc_list.item -- item from LIST yields the element at cursor position if s /= Void and then s.interest_rate > Result then -- Using and then (rather than and) ensures that -- s.interest_rate not evaluated if s = Void is true. Result := s.interest_rate end account_list.forth end end
Note that if there is no savings account at all in the list, the assignment attempt always yields void so that the result of the enclosing function is 0, the default initialization.
Assignment attempt is useful in the cases citedaccess to external objects beyond the softwares own control and access to specific properties in a polymorphic data structure. The form of the instruction precisely serves these purposes; not being a general type comparison (but only a verification of a specific expected type), it does not carry the risk of encouraging developers to revert to multibranch instruction structures, for which Eiffel provides the far preferable alternative of polymorphic, dynamically bound feature calls.
The final property of Eiffel inheritance involves the rules for adapting not only the implementation of inherited features (through redeclaration of either kind, redeclaration and redefinition, as seen so far) and their contracts (through the assertion redeclaration rule), but also their types. More general than type is the notion of a features signature, defined by the number of its arguments, their types, the indication of whether it has a result (that is, is a function or attribute rather than a procedure) and, if so, the type of the result.
In many cases, the signature of a redeclared feature remains the same as the originals. But in some cases, we may want to adapt it to the new classfor example, if we assume that class ACCOUNT has features
owner: HOLDER set_owner (h: HOLDER) is -- Make h the account owner. require not_void: h /= Void do owner := h end
Assume that we introduce an heir BUSINESS_ACCOUNT of ACCOUNT to represent special business accounts, corresponding to class BUSINESS inheriting from HOLDER (see Figure 9.13).
FIGURE 9.13. Parallel hierarchies.
Clearly, owner must be redefined in class BUSINESS_ACCOUNT to yield a result of type BUSINESS; the same signature redefinition must be applied to the argument of set_owner. This case is fully typical of the general scheme of signature redefinition: In a descendant, you may need to redefine both query results and routine arguments to types conforming to the originals. This is reflected by a language rule:
Covariance rule:
In a feature redeclaration, both the result type if the feature is a query (attribute or function) and the type of any argument if it is a routine (procedure or function) must conform to the original type as declared in the precursor version.
The term covariance reflects the property that all typesthose of arguments and those of resultsvary together in the same direction as the inheritance structure.
If a feature such as set_owner has to be redefined for more than its signatureto update its implementation or assertionsexplicit signature redefinition is acceptable. For example, set_owner could do more for business owners than it does for ordinary owners. Then the redefinition is of the form
set_owner (b: BUSINESS) is -- Make b the account owner. ... New routine body ... end
Previous | Table of Contents | Next |