Previous Table of Contents Next


The more general view is that each class describes a well-understood abstraction, for which the class designer decides exactly what operations are permitted. The class documentation (the short form described in section 9.8.5) makes this view clear to client authors; no violation of that interface is permitted, as it would make a mockery of the principles of object technology. This approach also paves the way for future generalization (9.3.3) of the most promising components and their inclusion into reusable libraries.

9.7. Genericity

Some of the classes that we need, particularly (but not solely) in libraries, are container classes, describing data structures made of a number of objects of the same type, or compatible types. Examples of containers include arrays, stacks, and lists. The class DEPOSIT_LIST posited in earlier examples describes containers.

It is not hard, with the mechanisms seen so far, to write a class such as DEPOSIT_LIST, which includes such features as count (query returning the number of elements) and put (command to insert a new element).

Most of the operations, however, are the same for lists of objects other than deposits. To avoid undue replication of efforts and promote reuse, we need a way to describe generic container classes, which can be applied to describe containers of elements of many different types.

The notation

   class C[G] ... The rest as for any other class declaration ...

introduces such a generic class. A name such as G appearing in brackets after the class name is known as a formal generic parameter; it represents an arbitrary type.

Within the class text, feature declarations can freely use G even though it is not known what type G stands for. Class LIST of ISE’s EiffelBase libraries, for example, includes features

   first: G
        -- Value of first list element
   extend (val: G) is
        -- Add a new element of value val at end of list
       ...

The operations available on an entity such as first and x, whose type is a formal generic parameter, are the operations available on all types: use as source y of an assignment x := y, use as target x of such an assignment (although not for val, which as a formal routine argument is not writable), use in equality comparisons x = y or x /= y, and application of universal features from ANY such as clone, equal, and copy.

To use a generic class such as list, a client provides a type name as an actual generic parameter; for example, instead of using DEPOSIT_LIST, the class ACCOUNT could include the declaration

   all_deposits: LIST [DEPOSIT]

using LIST as a generic class and DEPOSIT as the actual generic parameter. Then all features declared in LIST as working on entities of type G work, when called on the target all_deposits, on entities of type DEPOSIT. With the target

   all_accounts: LIST [ACCOUNT]

these features would work on entities of type ACCOUNT.

A note on terminology: To avoid confusion, Eiffel literature uses the word argument for routine arguments, reserving parameter for the generic parameters of classes.

Genericity reconciles extendibility and reusability with the static type checking demanded by reliability. A typical error, such as confusing an account and a deposit, is detected immediately at compile time because the call all_accounts.extend (dep) is invalid for dep declared of type DEPOSIT. (What is valid is something like all_accounts.extend (acc) for acc of type ACCOUNT.) In other approaches, the same effect might require costly runtime checks (as in Java or Smalltalk) with the risk of runtime errors.

Further flexibility is provided by providing a constrained form of genericity that allows assuming other operations, on a formal generic parameter, than just those of ANY. This will be seen in the discussion of inheritance.

An example of generic class from the kernel library is ARRAY [G], which describes direct-access arrays. Features include

  put to replace an element’s value, as in my_array.put (val, 25), which replaces by val the value of the array entry at index 25.
  item to access an entry, as in my_array.item (25) yielding the entry at index 25. A synonym is infix “@” so that the same result can be obtained more tersely as my_array @ 25.
  lower, upper, and count: queries yielding the bounds and the number of entries.
  The creation procedure make, as in !! my_array.make (1, 50), which creates an array with the given index bounds. It is also possible to resize an array through resize, keeping of course the old elements. In general, the Eiffel method shuns built-in limits and favors automatically resizable structures.

The comment made about INTEGER and other basic classes applies to ARRAY too: Eiffel compilers know about this class and can process expressions of the form my_array.put (val, 25) and my_array @ 25 in essentially the same way as a C or Fortran array access (my_array [25] in C). It is consistent and practical to let developers treat ARRAY as a class and arrays as objects; many library classes in EiffelBase, for example, inherit from ARRAY. Once again, the idea is to get the best of both worlds: the convenience and uniformity of the object-oriented way of thinking and the efficiency of traditional approaches. A similar technique applies to another kernel library class, one that is not generic: STRING, describing character strings with a rich set of string manipulation features.

The introduction of genericity brings up a small difference between classes and types. A generic class C is not directly a type because you cannot declare an entity as type C (you must use some actual generic parameter T—itself a type). Rather, C is a type pattern. To obtain an actual type C [T], you must provide an actual generic parameter T. This is known as a generic derivation. (T itself is, recursively, a type—either a non-generic class or again a generically derived type D [U] for some D and U, as in LIST [ARRAY [INTEGER]].)

It remains true, however, that every type is based on a class. The base class of a generically derived type C [T] is C.


Previous Table of Contents Next