- 18.07 (released) ...
- Basic types
- Optimizing array computations
- Tuple expressions
- Tuple features
- What have we gained?
- Active, iterators, numerical applications, introspection
- Temporary limitations
- String descriptor
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.
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.
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
To access and change the item at index i in array a, you may use features
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]
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
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
-- The critical loop:
index := some_initial_index
index = some_final_index
x := direct_access.item (index)
direct_access.put (some_value, index)
This replaces an original loop where the operations were on
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.
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.
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.
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]
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 [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.)
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
] are replaced by parentheses for the tuple representing the actual argument list of a routine call (see section 4).
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
ithere 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 becomes
with Arguments defined as
where the Tuple_expression uses the form given in section 2 but with the outermost
] delimiters replaced by parentheses to conform to usual practice. So the call
f (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 form
g ([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.
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 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.
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:
s1 := s
s3 := s.twin
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.