Language-related facilities

A number of classes offer facilities which are very close to the language level. Here too the book Eiffel: The Language covers the classes in detail, so we can satisfy ourselves with a quick summary; the flat-short forms appear in part C.

Basic types

The basic types BOOLEAN , CHARACTER , INTEGER , REAL and DOUBLE are defined by classes of the Kernel library.

In reading the class specifications for the numeric types INTEGER , REAL and DOUBLE , you might think that the type declarations are too restrictive. For example the addition operation in class REAL reads infix "+" (other: REAL): REAL

but there is actually no problem here. A language convention applicable to all arithmetic expressions, the Balancing rule, states that in any such expression all operands are considered to be converted to the heaviest type, where DOUBLE is heavier than REAL and REAL is heavier than INTEGER . So mixed-type arithmetic, consistent with common practice, is possible and indeed frequent.

Arrays

To create and manipulate one-dimensional arrays, use class ARRAY of the Kernel Library. Arrays are not primitive language elements; instead, they are handled through class ARRAY . This class is 'normal' in the sense that it may be used just as any other class by client and descendant classes. It is also somewhat special, however, in that the Eiffel compiler knows about it and uses this knowledge to generate efficient code for array operations.

To create an instance of ARRAY , use the creation instruction create my_array.make (1, u)

where the arguments indicate the lower and upper bounds. These bounds will then be accessible as my_array .lower and my_array .upper. The number of items is my_array .count; feature capacity is a synonym for count. The class invariant expresses the relation between count, lower and upper.

To access and change the item at index i in array a, you may use features item and put, as in x := my_array.item (i) my_array.put (new_value, i)

Function item has a bracket alias, so that you may also write the first assignment above more concisely as x := my_array [i]

Features item and put have preconditions requiring the index ( iin the above calls) to be within the bounds of the array. This means that you can detect bounds violations (which correspond to bugs in the client software) by using a version of class ARRAY compiled with precondition checking on. The bounds of an array may be changed dynamically through procedure resize. Previously entered elements are retained. Rather than an explicit resize, you may use calls to procedure force which has the same signature as put but no precondition; if the index is not within the current bounds force will perform a resize as necessary.

Optimizing array computations

CAUTION: Although ARRAY benefits from an efficient implementation, its more advanced facilities such as resizing do not come for free. For extensive computations on large arrays, an optimization may be desirable, bypassing these facilities. The technique yields loops that run at about the same speed as the corresponding loops written in C or Fortran (the usual references for array computations). It is of interest for advanced uses only, so that you may safely skip this section on first reading unless your domain of application is numerical computation or some other area requiring high-performance array manipulations.

The optimization relies on the class SPECIAL, used internally by ARRAY but of no direct interest to client developers in most common uses. With the declarations my_array: ARRAY [SOME_TYPE] direct_access: SPECIAL [SOME_TYPE]

you may use direct_access in lieu of 'my_array' within a critical loop, provided none of the operations may resize the array. Typically, the operations should only include put and item. In such a case you can use the following scheme: direct_access:= my_array.area -- The critical loop: from some_initialization index := some_initial_index until index = some_final_index loop ... x := direct_access.item (index) ... direct_access.put (some_value, index) ... end

This replaces an original loop where the operations were on my_array. Feature area of ARRAY gives direct access to the special object, an instance of SPECIAL, containing the array values. Features put and item are available in SPECIAL as in ARRAY , but without the preconditions; in other words, you will not get any bounds checking. Instances of SPECIAL are always indexed from zero, in contrast with arrays, whose lower bound is arbitrary, 1 being the most common value. But rather than performing index translations (that is to say, subtracting my_array .lower from index throughout the loop) it is preferable to use the following simple technique: if the lower bound 'lb' of my_array is 1 or another small integer, use 0 as lower bound instead when creating my_array, but only use the positions starting at 'lb'. You will waste a few memory positions (0 to lb-1), but will not have to change anything in your algorithm and will avoid costly subtractions.

It is important to note that this optimization, if at all necessary, should at most affect a few loops in a large system. You should always begin by writing your software using the normal ARRAY facilities; then once you have the certainty that the software is correct, if you detect that a large array computation is hampering the efficiency of the system, you may apply the above technique to get the fastest performance out of that computation. The change to the software will be minimal - a few lines - and will be easy to undo if necessary.

Tuples

A new Kernel Library class is introduced: TUPLE .

Alone among all classes, class TUPLE has a variable number of generic parameters. TUPLE, TUPLE [X], TUPLE [X, Y], TUPLE [X, Y, Z] and so on are all valid types, assuming valid types X, Y, Z and so on.

Conformance rules: [CONF1] For n >= 0 TUPLE [U1, U2, ..., Un, Un+1] conforms to TUPLE [U1, U2, ..., Un]

(and hence to TUPLE [T1, T2, ..., Tn] if each of the Ui conforms to each of the Ti for 1 <= i <= n.)

In particular all tuple types conform to TUPLE , with no parameter. [CONF2] For n >= 0 If *every* one of the types T1, T2, ..., Tn conforms to a type T, then TUPLE [T1, T2, ..., Tn] conforms to ARRAY [T]

Definition -- Tuple Type: A "tuple type" is any type based on class TUPLE , i.e. any type of the form TUPLE [T1, T2, ..., Tn] for any n (including 0, for which there is no generic parameter).

Note: CONF1 should be understood in terms of the underlying mathematical model.
Mathematically, TUPLE [T1, T2, ..., Tn] is the set TUPLE n of all partial functions f from N+ (the set of non-negative integers) to T1 U T2 U ... Tn, such that:
The domain of f contains the interval 1..n (in other words, f is defined for any i such that 1 <= i <= n).
For 1 <= i <= n, f (i) is a member of Ti.

With this definition, TUPLE n is indeed a subset of TUPLE n+1, and in particular TUPLE 0, the empty set, is a subset of TUPLE n for any n.)

Semantics: an instance of TUPLE [T1, T2, ..., Tn] is a tuple whose first element is an instance of T1, the second element being an instance of T2 etc. (The precise definition is the mathematical one given in note 1.) Note that there can be more than n elements to the tuple: for example a tuple with first element 5 and second element "FOO" is an instance of all of the following tuple types: TUPLE; TUPLE [INTEGER]; TUPLE [INTEGER, STRING].

It may seem restrictive at first to permit only one class, TUPLE , to have an arbitrary number of actual generic parameters. Why not have a general mechanism for declaring any class C so that all of C [X], C [X, Y] etc. are valid? But in fact this is not really a restriction. To obtain this effect without any complicated language convention, just declare C as C [G -> TUPLE] and then use the generic derivations C [TUPLE [X]] C [TUPLE [X, Y]] and so on. This also makes it possible to have the effect of some fixed parameters and some variable ones, as in C [G, H, I -> TUPLE] so we have all the necessary flexibility.)

Tuple expressions

Let e1, e2, ..., en be expressions of respective types T1, T2, ..., Tn. Then the expression [e1, e2, ..., en] denotes an instance of TUPLE [T1, T2, ..., Tn], whose first element is e1, the second element being e2, etc.

Tuple expressions can be nested: whereas [1, 2, 3] is a tuple with three elements (representing an instance of TUPLE [INTEGER, INTEGER, INTEGER]), [1, [2, 3]] is a tuple with two elements, the second one itself a tuple; the overall expression represents an instance of TUPLE [INTEGER, TUPLE [INTEGER, INTEGER]].

As a special case of tuple expression syntax, the delimiters [ and ] are replaced by parentheses for the tuple representing the actual argument list of a routine call (see section 4).

Tuple features

The exact specification of class TUPLE will be described in an addition to ELKS. The principal features are:

  • count (number of significant elements)
  • item (i), with the obvious precondition: the i-th element, of type ANY (since the value of i is not known at compile time); also first, second, third, fourth and fifth, of the appropriate types.
  • put (x, i), with the obvious precondition: replace i-th element with x. If argument x is not of the appropriate type T i there is no effect.
  • is_equal : redefined to consider only the first n elements, where n is the smaller length.

Other features under consideration include:

  • stripped (i): a tuple of type TUPLE [T1, T2, Ti-1, Ti+1, ..., Tn], derived from the current one by removing the i-th component, again with the obvious precondition.
  • wedged (x, i): a tuple with one more element, inserted at position i.
  • infix "+": tuple concatenation
  • infix "++": element concatenation; t ++ x is the same thing as t.wedged (x, t.count + 1).

What have we gained?

First we have solved the only case in the Eiffel programming language in which an expression has no precisely defined type: polymorphic manifest arrays. We don't have manifest arrays any more, but manifest tuples, with a precisely defined type. No incompatibility is introduced thanks to rule CONF2. The original syntax for manifest arrays, Result := <<e1, e2, ..., en>>, will continue to be supported. Second, we can define functions that return multiple results. This is a quite significant increase in expressive power. No common language has that. (You have to go to Lisp and functional languages.) Just define TUPLE [...] as the result type; in the function, you will write things like Result := [e1, e2, ..., en] Also, from a theoretical viewpoint, feature calls are simpler and more homogeneous: every feature takes exactly one tuple as argument and returns exactly one tuple as a result. (Either of these tuples may be empty: the first for a feature with no argument, the second for a procedure.) The syntax for a call becomesFeature Arguments

with Arguments defined asTuple_expression

where the Tuple_expression uses the form given in section 2 but with the outermost [ and ] delimiters replaced by parentheses to conform to usual practice. So the callf (a, b, c)

which we continue to think of as having three arguments a, b and c, formally has only one tuple argument [a, b, c]. This is of course not to be confused with a call of the formg ([a, b, c])

which has one argument (a tuple with three elements) in both the ordinary and the formal sense.

Active, iterators, numerical applications, introspection

For a set of important applications of tuples see the book chapter on agents and iterators which also covers aspects of numerical software and related topics following from the tuple mechanism.

Temporary limitations

The implementation of tuples has the following limitations:

  • Conformance of ARRAY types to TUPLE types is not yet fully supported.
  • Class TUPLE does not have features such as first and second. You must use item and, in most cases, an object test.

Strings

Strings are handled by class STRING , similar in many respects to ARRAY . Strings are of arbitrary size. The make creation procedure takes an integer argument, as in: s, s1, s2, s3: STRING ... create s.make (30)

The argument indicates the number of characters for the initial allocation. This is not an absolute limit: the string will automatically grow or shrink as a result of future operations. You may always request a resizing explicitly by calling procedure resize.

String descriptor

The object attached at run-time to an entity such declared of type STRING is not the actual sequence of characters but a string descriptor, which contains a reference to the actual string contents.

As a result, four assignment or assignment-like operations are possible:

  • A1 s1 := s
  • A2 s2.share (s)
  • A3 s3 := s.twin
  • A4 s4.copy (s)

As illustrated below, A1 is a reference assignment: s1 will be attached to the same descriptor as s. A2 keeps the descriptors distinct, but make them refer to the same sequence of characters. In A3, s3 will be attached to a new string, completely distinct from the string attached to s1 although made of identical characters. A4 has almost the same effect as A3, but is only applicable if s4 was not void, and will override the existing descriptor rather than creating a new one.

  

fig. 1: Effect of string assignment and copy operations

BASIC_ROUTINES provides a number of conversion functions, such as charconv.