Eiffel for beginners

Copyright © Simon Parker, 1992, 1993, 1999
Version 3.2

Classes

An Eiffel program is known as a system, and its components are classes. There may be any number of classes in a system. Many will be general classes, of value in different contexts. Some will be classes written specially for the system, but a fundamental goal of Eiffel is to help build reusable software.

An Eiffel development system comes with libraries of general-purpose classes. Many basic classes are defined with the language, and must be present. Others may be provided with the compiler, or bought from independent suppliers. During the course of developing successive projects, an organisation accumulates classes which match the kind of application it deals with.

The full text of an example class ENTRY is reproduced in Figure 1.

class
    ENTRY
            -- One entrant's time in a race

feature
    entrant: STRING
            -- Entrant, identified by sailnumber

    elapsed_seconds: INTEGER
            -- Actual elapsed time, not adjusted
        
    elapsed_minutes: REAL is
            -- Actual elapsed time, not adjusted
            -- in minutes and decimal minutes
        do
            Result := elapsed_seconds / 60
        end -- elapsed_minutes
        
    status: INTEGER
            -- Status of entrant in this race
        
    dnc: INTEGER is 0
            -- Status value, 'Did not compete'
        
    ok, rtd, dsq: INTEGER is unique 
            -- Status values:
            -- Finished, Retired, Disqualified

    finish (in_time: INTEGER) is
            -- Entrant finished. Note elapsed time, in seconds
        do
            elapsed_seconds := in_time
            status := ok
        end -- finish

    retire is
            -- Entrant retired from race
        do
            status := rtd
        end -- retire

    disqualify is
            -- Do not score this entry
        do
            status := dsq
        end -- disqualify

    started: BOOLEAN is
            -- Did the entrant start the race?
        do
            Result := status = ok or status = rtd
        end -- started

    finished: BOOLEAN is
            -- Did the entrant finish the race?
        do
            Result := status = ok
        end -- finished

    retired: BOOLEAN is
            -- Did the entrant retire?
        do
            Result := status = rtd
        end -- retired

    disqualified: BOOLEAN is
            -- Was the entrant disqualified?
        do
            Result := status = dsq
        end -- disqualified

    identify (sailnumber: STRING) is
            -- Record or change the entrant
        do
            entrant := sailnumber
        end -- identify

    identity: STRING is
            -- Readable form of entrant, even if none
        do
            Result := entrant
            if entrant = Void then
                Result := "(Unidentified)"
            end
        end -- identity

end -- class ENTRY

Figure 1

This nautical example is part of a system for scoring dinghy races. I have modified it for teaching purposes, but it is representative of the style of Eiffel programming and it compiles and executes on my machine. The class contains examples of everything discussed below. Look at it now, and refer to it as you read.

A simple class consists of a header, some features and an end delimiter. Eiffel syntax is not very demanding - much of the decoration is due to convention. Double hyphen introduces a comment, which lasts to the end of the line. Class headers and feature headers should be followed by a terse, useful comment. End delimiters should echo the corresponding headers. Class and type names are written in uppercase, most other words are in lowercase. All the punctuation here is mandatory. I invented the name of the class and the names of the features, but all the other words are reserved.

Features

Features are either attributes or routines. Routines are either procedures or functions.

The first two features are both attributes. The first can hold an arbitrary character string, the second an integer. Other basic types include REAL and DOUBLE for floating point numbers, and BOOLEAN for truth values.

The third feature, elapsed_minutes, is a function returning a floating point number. The signature is similar to those of the attributes, but it has a body introduced by the keyword 'do'. The similarity is deliberate, a consequence of applying the principle of information hiding.

The value returned by a function is whatever value is in 'Result' when the function ends. 'Result' is declared and initialised automatically at the start of the function, and you may change its value as many times as you want. In this case, the statement in the routine body assigns the value of a numeric expression to Result. Note that the assignment operator is ':=', and the equal sign alone always tests for equality.

status is an integer attribute. Like all numeric attributes, it will be initialised automatically to zero by default. The four declarations following status are also attributes, since they have no routine bodies. They all have literal values, however, which makes them constant attributes. The compiler will substitute the values for the names wherever they are mentioned. The value of the attribute dnc is explicitly stated as zero, matching the default initial value of status. ok, rtd and dsq will be given arbitrary sequential values by the compiler, guaranteed greater than zero and unique in this class.

Routine finish takes an integer argument, as shown by the parentheses. Because it has no return type it is a procedure rather than a function. The two statements in its routine body may be separated by a semi-colon if you want, but no punctuation is required at all. Procedures retire and disqualify are also procedures, but take no arguments. The next group of boolean functions provide the means to test the status of this entry. Note the use of the operator 'or' in function started; operator precedence rules ensure this expression is interpreted as you would expect.

Types

Before considering the last two features of this class, we need a little background on objects and types.

A named place where a value can be kept, typically in memory, is called an entity. We have encountered three of the four kinds of entity:

Every entity has a static type. For attributes and arguments the type is part of the declaration. The type of Result is implied by the signature of the function in which it is defined.

An entity can only hold values of its own type. If you assign a string value to an integer entity like elapsed_seconds at run time, the assignment will fail. This is called type failure: in production software it is disastrous.

Eiffel is a statically typed language. It has been designed in such a way that it is always possible for the compiler to verify that a type failure cannot happen.

The entities we have seen so far, types INTEGER, REAL and BOOLEAN, are all big enough to contain the corresponding simple numeric values. These types are called expanded types.

The attribute entrant has the type STRING. Character strings vary in length and are too complex to keep in an entity. STRING objects are therefore stored elsewhere, and the entity contains only a reference to the place where the object is stored. STRING is an example of a reference type.

Expanded types and reference types behave differently in some situations. If an object of an expanded type is assigned to an entity,

    elapsed_seconds := in_time

a fresh copy of the number is assigned to the attribute. Doing the same with a string:

    entrant := sailnumber

just creates a new reference to the same string. You will find that most types are reference types, so it is worth getting used to this behaviour.

When an entity is created it is always initialised. For numbers, the initial value is zero. Boolean entities are always set to False. All entities of reference types are set to a special value called Void, indicating they do not refer to any object. We say they are void references, and the final function identity shows how to test for this condition. It also contains an example of a string constant, and contains two assignments to Result.

Supplier and consumers

One of Eiffel's design goals is to support the large scale reuse of software. In this context there is an important distinction between the supplier of a class and the consumer.

Both roles are played by software designers, but with different perspectives. The supplier is concerned with encapsulating useful abstractions elegantly and efficiently. The consumer is concerned with selecting and combining classes to build higher level software with as little new code as possible.

As software is built in successive layers the Eiffel programmer usually has a foot in both camps. These roles are played by the classes as much as by the designers. The term client implies the consumer perspective in any case.

Libraries

The Eiffel libraries illustrate this distinction. They contain a rich collection of software, all built for other people to use. Each compiler vendor provides libraries addressing fundamental programming needs like data structures, maths, input and output. With the first commercial Eiffel compiler, ISE bundled eight libraries containing hundreds of classes. To give you a feel for the scale of this software here is an arbitrary selection of class names.

Calling features

How do clients use this software? The programmer learns how to use libraries from documentation, of which more later. Application code uses classes by creating objects and calling features. Most Eiffel programming consists of writing feature calls.

The Eiffel feature call uses a 'dot' notation. For example,

    ff_start_time.adjust (0, 20)

Here, ff_start_time is an entity referring to a TIME_OF_DAY object. The effect of the call is to advance the minute value by 20. Figure 2 shows the full text of class TIME_OF_DAY.

class
    TIME_OF_DAY
            -- Absolute time within a day to the nearest minute

feature
    hour: INTEGER is
            -- Hour of day, 00 to 23
        do
            Result := minutes // 60
        end -- hour

    minute: INTEGER is
            -- Minute in hour, 00 to 59
        do
            Result := minutes \\ 60
        end -- minute

    set (hh: INTEGER, mm: INTEGER) is
            -- A new time
        require
            0 <= hh and hh < 24
            0 <= mm and mm < 60
        do
            minutes := hh * 60 + mm
        ensure
            hour = hh
            minute = mm
        end -- set

    adjust (hh_by: INTEGER, mm_by: INTEGER) is
            -- Advance (+) or retard (-)
            -- either unit or both
        do
            minutes := minutes + hh_by * 60 + mm_by
            normalise
        end -- adjust

feature {NONE}
    minutes: INTEGER
            -- Minutes since midnight

    normalise is
            -- Restore invariant after adjustment
        do
            minutes := minutes \\ 1440
            if minutes < 0 then
                minutes := minutes + 1440
            end
        end -- normalise

invariant
    0 <= hour and hour < 24
    0 <= minute and minute < 60
    0 <= minutes and minutes < 1440

end -- class TIME_OF_DAY

Figure 2

Actual arguments in the call must match the formal arguments in the signature in number, position and type. If the feature takes no arguments, there are no parentheses. For example,

    hh := ff_start_time.hour 

Visibility

The designer of a class controls which clients may call its features.

Feature declarations are organised in groups according to their visibility. Each group has a header which lists the classes which may use the features which follow it. A client may only call a feature if the client belongs to one of the classes in the list.

If the header has no list, then any client may call the features. If the list is empty, or contains the word NONE, then the features are for internal use only.

For example, clients of TIME_OF_DAY may not use features minutes and normalise because they are in an internal group.

Inside the class all features are always visible. The value of an attribute may be changed by any feature in the class, but never by any client.

The body of a routine is the private domain of the implementor. No client should depend on the internal details, but only on the public interface to the routine as expressed by its signature.

A signature alone provides essential information about how to call a feature. The types of the arguments and return value will be checked by the compiler for each call.

Assertions

Eiffel brings to practical commercial programming some important notions from the discipline of formal specification. Three clauses, all optional but strongly recommended, allow the supplier to specify rigorously much that is traditionally been left to hearsay, convention or 'Technical Documentation'.

The 'require' and 'ensure' clauses are associated with a routine, the 'invariant' clause with a class.

The require clause introduces a precondition for a routine. A precondition states the circumstances under which the routine may be called. It may impose restrictions on the values of arguments, as in procedure set of TIME_OF_DAY. It may refer to the state of the object. For example, in some class concerned with output you might find this:

    write (text: STRING) is
        require is_open
        ...

The ensure clause introduces a postcondition. A postcondition declares what effect the feature will have. It may refer to the value returned by the feature or to the state of the object after the call. The ensure clause for procedure set says precisely what the procedure does, without waffle. More signifantly, the clause can be executed.

The invariant clause states 'eternal truths' about the class. In theory, these conditions must always be true. In practice the rules are suspended while a routine is executing, but the routine must 'restore the invariant' before it returns. This is what routine adjust of class TIME_OF_DAY does by calling normalise.

Programming by Contract

You can think of preconditions and postconditions as a contract governing the use of a feature. The caller is responsible for the preconditions, and the feature is not obliged to work at all if they are not met.

On the other hand the feature is responsible for satisfying the postconditions and the caller can rely on them. Liability on each part is limited to the terms of this contract.

Because the require clause is executable, the routine need not repeat the checks. Because the clause is binding on the caller, the routine need not consider error handling. It only has to do the job it was designed for!

Similarly, the ensure clause allows the caller to proceed after the call confident that the work has been done. There is no need to check whether the call succeeded.

Documentation

An automatic documentation tool called 'short' reinforces these principles by producing a version of an Eiffel class with all implementation details suppressed. It retains all the interface information, but only the interface information. The idea is that client programmers can work from the 'short' version of a class.

A short version of class TIME_OF_DAY is shown in figure 3. Compare it carefully with the class text in figure 2.

class interface 
    TIME_OF_DAY
            -- Absolute time within a day to the nearest minute
feature
    hour: INTEGER
            -- Hour of day, 00 to 23

    minute: INTEGER
            -- Minute in hour, 00 to 59

    set (hh: INTEGER, mm: INTEGER)
            -- A new time
        require
            0 <= hh and hh < 24
            0 <= mm and mm < 60
        ensure
            hour = hh
            minute = mm

    adjust (hh_by: INTEGER, mm_by: INTEGER)
            -- Advance (+) or retard (-)
            -- either unit or both

invariant
    0 <= hour and hour < 24
    0 <= minute and minute < 60

end -- class interface TIME_OF_DAY

Figure 3

Assertions are part of the interface - both parties must see the contract! They therefore form a crucial part of the documentation, and are preserved in the short version of a class.

The Technical Documentation can devote itself to general discussion of how the classes and features are intended to be used. It might be brief and general enough to be read, and even maintained!

Creating Objects

You create an object by executing a creation instruction. In its simplest form it is a pair of exclamation marks followed by the name of the target entity.

    finish_time: TIME_OF_DAY;
    ...
        !!finish_time

The effect of executing this instruction is:

  1. Create a new object of type TIME_OF_DAY
  2. Initialise each attribute
  3. Replace the value of finish_time with a reference to the new object

TIME_OF_DAY objects have only one attribute (minutes) which is initialised to zero since its type is INTEGER. This produces a valid object; that is, it satisfies the invariant.

It is good Eiffel style to arrange that the default initialisation of each attribute will yield a valid object. In situations where this simple approach is inadequate, an alternative form of the creation instruction is used.

Consider class TIME_OF_DAY. The simple creation instruction above creates an object with a value of midnight. Clients will probably follow the creation instruction with a call to feature set:

    lunchtime: TIME_OF_DAY
    ...
    !!lunchtime
    lunchtime.set (12, 30)

We can save our clients the burden of writing a separate call by nominating procedure set (and possible others) as a creation procedure. Then the procedure can be invoked by including the feature name and arguments in the creation instruction:

    lunchtime: TIME_OF_DAY
    ...
    !!lunchtime.set (12, 30)

The effect of executing this instruction is:

  1. Create a new object of type TIME_OF_DAY
  2. Initialise each attribute
  3. Replace the value of lunchtime with a reference to the new object
  4. Call procedure set of the new object with arguments (12, 30)

The penalty our clients pay is that the simple form becomes illegal. The price the supplier pays is a new part in the class definition. The creation part comes before the first feature header, and the syntax is straightforward: the word 'creation' followed by a list of procedure names.

    class TIME_OF_DAY
            -- Absolute time within a day to the nearest minute

    creation
        set

    feature
        ...

New objects can also be created from old ones using a 'clone' call. Clone looks like a function, returning a new object which is a copy of its argument. For example:

    ff_start, ent_start: TIME_OF_DAY
    ...
        !!ff_start
        ...
        if starting_together then
            ent_start := clone (ff_start)
        end

System Execution

One of the distinguishing aspects of object-oriented designs is that the intelligence is well distributed.

A system consists of a collection of co-operating classes, each designed to do a particular job thoroughly. The importance of the controlling routine, the 'main program' is diminished.

During execution, Eiffel instructions create objects and call features of those objects. You cannot write an Eiffel routine outside a class. The first routine executed is the creation procedure of the first object, which typically creates other objects and calls other routines. When the creation procedure ends, the system terminates.

The first object is known as the root, and its class is the root class. The root class for a system, and the creation procedure to be used, are nominated outside the code in a project description using some vendor-specified syntax.

There is nothing special about a root class. It is simply a high level component, which can be used by client programmers to build software. This means that one user's entire application might become just a menu option in another user's system. For example, consider the components implied by the names 'admission_dialogue', 'booking_session' and 'account_enquiry'.

This makes for reliable and extendable architectures, and offers a powerful approach to managing the complexity of big software systems.

Inheritance

Inheritance is one of the core techniques of object-oriented programming, and in Eiffel it is used freely for various purposes.

The next few examples are taken from a small set of classes (a 'cluster') concerning time. The design of this cluster is based on the notion that units of time sometimes behave like the digits in a number. For example, class MIN12 represents the time of day to the nearest minute in the style "12:34 AM", as a three-digit number. Some of the classes in the cluster are listed in Figure 4, with inheritance relationships implied by indentation. Figures 5, 6 and 7 show the full text of classes DIGIT, DIGIT12 and MIN12 respectively. Fragments of other classes appear in the text.

DIGIT              -- Component of a number
    DIGIT2         -- Binary digit
        AMPM       -- Component of time: morning or afternoon
    DIGIT10        -- Decimal digit
    DIGIT12        -- Digit, base 12
        HOUR12     -- Hour of day, 12 then 1 to 11
        MON12      -- Month of year, January to December
    DIGIT24        -- Digit, base 24
    DIGIT60        -- Digit, base 60
    DIGITV         -- Digit, radix can change dynamically
NUMBER             -- Sequence of related digits
    NUM10          -- Decimal number
    HMAMPM         -- Time of day, as in 2:34 PM
    HMSAMPM        -- Time of day, as in 2:34:56 PM
    HMS24          -- Time of day, as in 14:34:56

Figure 4

Eiffel inheritance terminology has a genealogical flavour. HOUR12 is an heir of DIGIT12, and DIGIT12 is the parent of HOUR12. The ancestors of a class include the class, its parent, its grandparent and so on. The descendants include the class, all its heirs, all their heirs and so on. Note that these terms are inclusive - a class is its own ancestor and descendent.

To declare that class HOUR12 inherits from class DIGIT12, you write an 'inherit' clause in class HOUR12. Class DIGIT12 remains completely untouched.

class
    HOUR12
        -- Hour component of time of day

inherit
    DIGIT12

end -- class HOUR12

Renaming

This is a complete and valid Eiffel class declaration. It declares that HOUR12 is an heir of DIGIT12, and is identical except for the name.

Using inheritance to provide a more appropriate name for a class in some new situation is legitimate. This is not the only way to do it: your compilation system should also provide class name management facilities too.

In the same spirit, the client programmer may change the name of an inherited feature to something more suitable for clients.

If you were designing HOUR12 and decided that advance and retard were more meaningful to your clients than increment and decrement, you could include a 'rename' phrase in the inherit clause.

class
    HOUR12
            -- Hour component of time of day
inherit
    DIGIT12
        rename
            increment as advance,
            decrement as retard
        end

end -- class HOUR12

Visibility

The designer of the heir class gets unimpeded access to all the features of all its ancestors, whether they are exported or not. The designer of a new class is encouraged to reuse as much as possible, including the implementation as well as the interface.

The heir class determines the visibility of the inherited features to clients. An inherited feature retains its export status unless it is explicitly mentioned in an 'export' clause.

Class DIGIT declares a hidden feature more_sig holding a reference to the next digit. If you are designing an heir DIGITF and want to make more_sig visible to class NUMBER, you must write an export phrase in the inherit clause.

class
    DIGITF

inherit
    DIGIT
        export
            {NUMBER} more_sig
        end

end -- class DIGITF

You may write as many 'export' phrases as necessary, and they can mention any classes and any inherited features. You may hide visible features as well as revealing hidden ones.

Specialisation

Inheritance is most commonly used for specialisation. The heir implements the behaviour defined by the parent in some specialised way.

The real difference between HOUR12 and DIGIT12 is the way the values are presented. Feature symbol as inherited would present the hours as "0" to "11". We need the first hour to be presented as "12" instead, so we must provide a new implementation of symbol.

The new implementation is written in the 'features' part just like a new feature. The 'redefine' phrase in the inherit clause reassures the compiler that we know what we are doing.

class
    HOUR12
            -- Hour component of time of day
inherit
    DIGIT12
        redefine
            symbol
        end

feature
    symbol: STRING is
            -- 12, 1, 2... 10, 11
        do
            ...
        end -- symbol

end -- class HOUR12

It is very important not to abuse this mechanism to implement different behaviour, and Eiffel's pre- and postconditions have a significant role to play here. When a descendent redefines a routine, the inherited precondition and postcondition continue to govern the new definition. Any new assertions associated with the more specialised routines are combined with the inherited assertions in a manner which protects existing code. This is emphasised by the syntax: the preconditions and postconditions of a redefined routine are introduced by the words 'require else' and 'ensure then'.

Extension

Another reason for defining a new heir is to extend the capabilities of the parent by introducing new features.

There is no need to identify such features in the inherit clause, but if they are to be visible they must be exported. As with redefined routines, the new features must preserve the class invariant.

Class DIGITV needs extra features to vary the radix. Features set_highest, set_lowest and set_range are declared in the features part, but they are not mentioned in the inherit clause.

Common Features

In any significant programming project, there are general-purpose routines and constants which are useful in many situations. Many of these are not naturally associated with any identifiable abstraction. In traditional environments such routines are gathered in libraries and declared in client source text by various means.

Inheritance provides the equivalent mechanism in Eiffel. Routines and constants are gathered in classes whose only purpose is to make them available through inheritance. In OOP parlance, such classes are sometimes known as mixin classes.

Class GENERAL in the kernel library provides a range of facilities which are automatically inherited by all classes. This means that the simplest of classes you define will have some innate features. Here are some of the standard features; your vendor may also provide others, and you can add your own.

	is_equal, copy, clone
	deep_equal, deep_copy, deep_clone
	conforms_to
	maximum_character_code

Other basic classes supplied with the compiler will encapsulate mathematical and conversion routines. Input, output, exception handling and access to the internal representation of objects are also provided in this form.

Abstract Classes

Abstract classes allow the designer to model high-level concepts precisely without pre-empting physical design decisions.

Most abstract classes represent 'abstract data types', either general data types such as STRING or ARRAY, or application data types like PATIENT or HOSPITAL_STAY.

Other abstract classes encapsulate frameworks. A framework implements a general algorithm but leaves specific processing steps for specialised descendants to supply.

Some abstract classes simply gather together features which were common to two or more otherwise unrelated classes. Such classes might be produced in a generalisation stage near the end of a project.

There is little to distinguish an abstract class. The Eiffel inheritance mechanism and lenient syntactical requirements allow the designer to write legal Eiffel classes describing developing concepts at an early stage.

Two aspects of the language are particularly useful to the designer of abstract classes: deferred features and genericity.

Deferred Features

Usually the abstract class can provide some reasonable default implementation for descendants to redefine if necessary. Class DIGIT provides a default implementation of symbol, for example.

Often this is either not feasible or not appropriate. In this case you need to make sure that descendants will define the missing feature - and do so properly. You do this by writing a deferred feature.

A class containing a deferred feature is called a deferred class. You may not create instances of a deferred class, only inherit from it. To make this obvious, the class header is flagged.

deferred class
    DIGIT

A deferred feature has a name, comment and assertions like any other feature, but the word 'deferred' takes the place of a body or value. In class DIGIT, features lowest and highest are both deferred routines.

These features are more than just placeholders, or forward references. Their behaviour is specified by these signatures, and further constrained by expressions in the invariant of class DIGIT (Figure 5).

deferred class
    DIGIT
            -- Component of a number

feature
    make is
            -- Default creation feature for convenience
        do
        end -- make

    value: INTEGER
            -- current value

    symbol: STRING is
            -- Printable representation
        do
            Result := value.out
        end -- symbol

    connect (left: DIGIT) is
            -- Attach a more significant digit
        do
            more_sig := left
        end -- connect

    increment is
            -- Add 1 to this digit
        do
            if value < highest then
                value := value + 1
            else
                carry
                value := lowest
            end
        ensure
            value = old value + 1
            or value = lowest
        end -- increment

    increment_by (how_much: INTEGER) is
            -- Add how_much to value, carrying as necessary
        require
            positive: how_much >= 0
        local
            remains: INTEGER
        do
            from
                remains := how_much
            variant
                remains
            until
                remains < 1
            loop
                increment
                remains := remains - 1
            end
        end -- increment_by

    decrement is
            -- Reduce value by 1, borrowing if necessary
        do
            if value > lowest then
                value := value - 1
            else
                borrow
                value := highest
            end
        ensure
            value = old value - 1
            or value = highest
        end -- decrement

    decrement_by (how_much: INTEGER) is
            -- Subtract how_much from value, borrowing as
            -- necessary
        require
            positive: how_much >= 0
        local
            remains: INTEGER
        do
            from
                remains := how_much
            variant
                remains
            until
                remains < 1
            loop
                decrement
                remains := remains - 1
            end
        end -- decrement_by

feature {NONE}
    lowest: INTEGER is
            -- lowest legal value
        deferred
        end

    highest: INTEGER is
            -- highest legal value
        deferred
        end

    more_sig: DIGIT
            -- Next higher digit in number, if any

    carry is
            -- Overflowed this digit
        do
            if more_sig /= Void then
                more_sig.increment
            end
        end -- carry

    borrow is
            -- Underflowed this digit
        do
            if more_sig /= Void then
                more_sig.decrement
            end
        end -- borrow

invariant
    range_positive: highest >= lowest
    value_high_enough: lowest <= value
    value_low_enough: value <= highest

end -- class DIGIT

Figure 5

Any descendent which supplies implementations for the deferred features (in Eiffel terms, 'effective implementations') must abide by the conditions laid down here.

Class DIGIT12 (in Figure 6) is an heir which provides effective implementations for highest and lowest.

class
    DIGIT12
            -- Digit with base 12

inherit
    DIGIT

creation
    make,
    connect

feature
    lowest: INTEGER is once Result := 0 end

    highest: INTEGER is once Result := 11 end

end -- class DIGIT12

Figure 6

Generic Classes

Sometimes a supplier programmer designing an abstract class must deal with objects whose type only the client programmer will know. In this case the supplier offers a generic class, and the missing type information must be provided by parameter as part of an extended declaration. A generic class is not necessarily deferred; it may be used by clients without inheriting.

Suppose we are building a class representing a number, and want an array which can hold DIGIT objects. The ARRAY class from the basic libraries will serve our purpose. ARRAY is a generic class, and we must provide the type of the array elements in brackets as part of the declaration.

    digits: ARRAY [DIGIT]
    ...
        !!digits.make (1, 3)

The creation instruction creates an array with three elements, each of which can contain a reference to a DIGIT object. An attempt to store any other type, such as a STRING, will be rejected by the compiler as a type violation. On the other hand, any object obtained from this array is guaranteed to be a DIGIT object.

Class ARRAY is not called upon to do more than store and retrieve elements, and client programmers are free to use it for objects of any type. More specialised generic classes need the co-operation of the objects they manipulate, so more control is needed.

The supplier includes the name of a class, called a 'constraining class' in the interface of the generic class. The types which the client programmer may specify in the declaration are then restricted to descendants of the constraining class.

Suppose we are designing a new class SORTED_LIST. Somewhere in the text of the class will be an expression comparing elements. This can only work if the elements are of a type for which comparison is meaningful.

The behaviour of the relational operators ( <, >, =, ...) is specified in a small abstract class in the basic libraries called COMPARABLE. Other basic classes inherit from it, including STRING.

We can ensure that only descendants of COMPARABLE will be admitted to SORTED_LIST objects by making it the constraining class.

class
    SORTED_LIST [ET -> COMPARABLE]

Whenever we need to refer to the element type in class SORTED_LIST, we write ET. The signature of the function returning the first element would be written:

    first: ET

The code inserting a new element might contain code like this:

    this_element, that_element: ET
    ...
        if this_element > that_element then ...

Types

One of the fundamental principles of Eiffel is that a class is both a module and a type. Inheritance defines a type hierarchy, in which an heir defines a subtype of the parent.

A related concept is conformance; broadly, one type conforms to another if its corresponding class is a descendent of the other's. In any situation where an object of a certain type is required, an object of any conforming type can always be used.

This gives rise to the distinction between the static and dynamic types of an entity. The static type is the type written in the class text. The dynamic type is the type of the object it actually refers to at run time.

A programmer's reaction to the temptation to test and switch on the dynamic type of an object is a test of commitment to the object-oriented paradigm!

Dynamic Binding

The attribute more_sig in class DIGIT (Figure 5) holds a reference to the next digit in a number. This is to support procedure carry, which must increment it from time to time. The static type of more_sig is DIGIT.

Class MIN12 (Figure 7) combines three different subtypes of digit to show hour, minute and AM or PM. Its creation procedure creates hh, mm and ampm and connects them together.

class
    MIN12
            -- Time in 24 hours, to nearest minute, in 12 hour format

inherit
    NUMBER
        redefine
            out
        end

creation
    make

feature
    ampm: AMPM

    hh: HOUR12

    mm: DIGIT60

    make is
            -- Create and link digits
        do
            !!ampm.make
            !!hh.connect (ampm)
            !!mm.connect (hh)
        end -- make

    least_sig_digit: DIGIT is
        once
            Result := mm
        end -- least_sig_digit

    out: STRING is
            -- 12:34 AM
        do
            Result := clone(hh.symbol)
            Result.append_character (':')
            Result.append_string (mm.symbol)
            Result.append_character (' ')
            Result.append_string (ampm.symbol)
        end -- out

end -- class MIN12

Figure 7

After the connections are made the dynamic type of mm.more_sig is HOUR12, and the dynamic type of hh.more_sig is AMPM. Since both HOUR12 and AMPM conform to DIGIT, this is legal.

When procedure mm.carry calls more_sig.increment, the runtime system finds the version of increment appropriate for HOUR12 objects.

When hh.carry executes the call more_sig.increment, it is the version of increment in class AMPM which is invoked.

This dynamic binding mechanism is the source of much of the flexibility and power of object-oriented programming. In Eiffel it is supplemented by the security of strong typing. The type system ensures that there will always be at least one version of a feature available, and dynamic binding selects the right one.

Multiple Inheritance

Soon after you begin to think about object-oriented design you are likely to encounter a situation in which a new class wants to inherit behaviour from more than one parent. In many cases a review of the surrounding classes will reveal a simpler design involving only one parent, but multiple inheritance is often the most elegant and natural solution.

Eiffel does not discourage multiple inheritance; on the contrary, the technique is used widely.

Since the structuring unit in Eiffel is the class, and there are no global features, inheritance is the way to bring features of one class into the scope of another class.

It is common to package specialised behaviour in small classes which do not represent useful abstractions on their own. In order to augment the capabilities of your application classes with such 'mixins' without distorting the natural inheritance structure you need multiple inheritance.

Multiple inheritance is achieved by writing the names of all the parent classes in the 'inherit' clause. Each parent class name may be followed by 'rename' and 'redefine' phrases appropriate to that parent. The parents may be listed in any order, there may be any number of them, and they may be generic classes.

There is one major restriction: none of the parents may be a descendent of the class being defined. In other words, the 'inheritance hierarchy' may not contain cycles.

Parents might come from different lines of development, and even from different vendors. It is likely that the names of some of the inherited features will clash. The designer of the heir is obliged to resolve such clashes explicitly by renaming one or more of the inherited features.

Repeated Inheritance

Since multiple inheritance is permitted, it is also necessary to provide for repeated inheritance.

Direct repeated inheritance occurs when the same class is listed more than once in the inherit clause. This sounds an unlikely situation, but it is entirely legitimate and can be useful.

Indirect repeated inheritance means that two or more of the parents share a common ancestor. Since all Eiffel classes trace their origins back to ANY, multiple inheritance always introduces repeated inheritance.

As you might expect, Eiffel resolves the potential conflicts without fuss. Any feature which is inherited via more than one path, and ends up with the same name, becomes a single ('shared') feature in the heir. If it has been renamed along one path, the inherited features remain distinct, or 'replicated'.

In all other cases there is no obvious way to resolve the conflicts. Eiffel doesn't attempt to guess your intentions, so you need to make an explicit choice. There are options to help you, including renaming, undefining and changing visibility. The 'select' clause lets you resolve an ambiguity which arises in the case of dynamic binding.

Precursor

A interesting case of inheritance is class MON12 (Figure 8), which encapsulates the behaviour of the month component of a date. It implements one of the key mechanisms of this cluster, allowing the month to determine the highest value the day may assume.

class
    MON12
            -- Month of year, a component of a Gregorian date

inherit
    DIGIT12
        redefine
            symbol, increment, decrement
        end

creation
    make,
    connect_day

feature
    connect_day (dd: DIGITV) is
            -- Link to less significant digit
        do
            day := dd
            changed
        end

    increment is
            -- As parent, but note change
        do
            Precursor
            changed
        end -- increment

    decrement is
            -- As parent, but note change
        do
            Precursor
            changed
        end -- decrement

    full_name: STRING is
            -- Full name, in English
        do
            Result := full_names.item (value + 1)
        end -- full_name

    symbol: STRING is
            -- Full name
        do
            Result := full_name
        end -- symbol

feature {NONE} -- new features, private
    day: DIGITV
            -- Day of month, we control range
            -- Less significant digit

    changed is
            -- Month has changed: update day range
        do
            if day /= Void then
                inspect value + 1
                when 1, 3, 5, 7, 8, 10, 12 then
                    day.set_range (1, 31)
                when 2 then
                    day.set_range (1, 28)    -- leap?
                when 4, 6, 9, 11 then
                    day.set_range (1, 30)
                end
            end
        end -- changed

    full_names: ARRAY [STRING] is
            -- Full month names
        once
            Result := <<"January", "February", "March",
                "April", "May", "June",
                "July", "August", "September",
                "October", "November", "December">>
        end

end -- class MON12

Figure 8

A hidden feature changed keeps the day and month in step, and must be called whenever the month is incremented or decremented. Features increment and decrement do this, so we need to add one statement to each of these features.

As far as it goes, the behaviour of increment and decrement inherited from DIGIT is fine. Duplicating the text here would introduce the kind of maintenance problem good object-oriented programming avoids, as well as giving us more work to do.

We must certainly redefine the inherited increment and decrement, but we want our new versions to call the inherited versions too. The solution is to call Precursor, which invokes the inherited version of the present routine.

Input and Output

A library class STD_FILES provides convenient access to the standard input and output files. In Eiffel, all classes inherit from class ANY. Even if no inheritance is specified, the class will implicitly inherit from ANY, and thus class ANY represents the top of the inheritance hierarchy for any user-defined classes. ANY itself inherits from class GENERAL which provides several features that are considered universally useful, regardless of the class you are writing. These features are then automatically inherited into every class you write. One of these is a feature of type STD_FILES called io. The universally available io feature provides us with a convenient way to do console input and output.

Figure 9 shows a class FILTER, which copies input to output one line at a time. It uses features read_line, put_string and last_string from class STD_FILES.

class
    FILTER

creation
    run

feature
    run is
            -- Echo until end of file
        do
            from
                io.read_line
            until
                equal (io.last_string, "")
            loop
                io.put_string (io.last_string)
                io.put_new_line
                io.read_line
            end -- loop
        end -- run

end -- class FILTER

Figure 9

Once Routines

Consider the declaration of the feature io from class GENERAL above. The short form of the feature will be as follows:

    io: STD_FILES

In keeping with the principle of uniform access, we cannot tell from the short form whether this is an attribute or a function with no arguments. Which would be best?

Implementing io as an attribute would work but would be quite inconvenient, as every class would have to create the STD_FILES object before using it:

    !!io.make

Implementing io as a function will remove the necessity to have to create the object in descendants:

    io: STD_FILES is
            -- used for console I/O
        do
            !!Result.make
        end -- io

This is more convenient as we can just use io whenever we want. It does have one significant drawback though. Every time we use the io feature, a new object of type STD_FILES gets created. This will lead to an unnecessary profusion of objects. The Eiffel solution in such a situation is to use a once function. The actual definition of io in class GENERAL is as follows:

    io: STD_FILES is
            -- used for console I/O
        once
            !!Result.make
        end -- io

The first time any routine in this system refers to 'io' it executes normally, creating a new STD_FILES object and returning a reference in Result. After that, any call of feature 'io' returns the same value without executing the body again. In effect, 'io' behaves like a function the first time and a read-only attribute thereafter.

The once routine provides a safe and elegant alternative to a variety of traditional mechanisms, notably constants of class types, initialisation routines, global and shared variables. Program design is simpler, and more versatile and flexible structures become possible.

Memory Management

Objects are created by creation instructions, but we have not tackled the question of how they die.

The obvious solution is to provide a means for the programmer to kill them. This is easy for the language implementor, but leads to serious difficulties for the application programmer.

Objects created in one part of the system may be destined for use elsewhere. The code responsible for creating an object may not be aware when it is no longer needed. When one part of the system has finished with an object it cannot be certain it is not still being used elsewhere.

Objects might contain mutual references to each other even after they have been discarded by the application which used them.

These considerations make memory management a burden too heavy for the programmer, and Eiffel provides no 'delete' or 'free' statement by which the programmer can cause objects to die. Instead, the Eiffel runtime system automatically tracks references to objects and reclaims space when it is safe to do so. The process is called 'garbage collection'.

You can help the garbage collector by explicitly discarding references which you no longer need by assigning Void to them, but most references (not objects) vanish at routine exit anyway.

There are situations in which even the garbage collector's carefully controlled interruption cannot be tolerated, and for such applications classes in the basic libraries contains features allowing you to control it.

Persistence

An executing Eiffel system represents application data as a collection of collaborating objects, connected by a potentially complex web of references.

With current technology, these objects occupy volatile memory in the computer, and vanish when the system terminates. Persistence extends the lifetime of an object from one execution of a system to the next. It is achieved by saving the object to a file on secondary storage in one session and restoring it from the file in another.

In Eiffel, persistence is achieved using the library class STORABLE. The two main features of interest from this class are basic_store and retrieved. Their short forms are as follows:

    basic_store (file: FILE)
            -- Produce on 'file' an external representation of the
            -- entire object structure reachable from current object.
            -- Retrievable within current system only
        require
            file_not_void: file /= Void
            file_exists: file.exists
            file_is_open_write: file.is_open_write

    retrieved (file: FILE) : STORABLE
            -- Retrieved object structure, from external
            -- representation previously stored in 'file'.
            -- To access resulting object under correct type,
            -- use assignment attempt.
            -- Will raise an axception (code Retrieve_exception)
            -- if file content is not a STORABLE structure
        require
            file_not_void: file /= Void
            file_exists: file.exists
            file_is_open_read: file.is_open_read
        ensure
            result_exists : Result /= Void

To save an object, call feature basic_store giving a FILE object as argument. The object is converted to a suitable (private) external representation and stored in a new file with the specified name. For example,

    data_file: FILE

        ...
        data_file.make_create_read_write ("prefer.dat")
        w_preferences.basic_store (data_file)

It is not only the object referred to by 'w_preferences' which is saved, but all the other objects on which it depends directly or indirectly. The entire structure is retrieved at once by the corresponding retrieved feature.

     retrieved (data_file)

Assignment Attempt

Detaching an object from the context in which it was created is a risky operation, since the language system can no longer monitor and enforce the typing rules.

The retrieved object is returned by a function with return type STORABLE. The actual dynamic type may be any descendant of STORABLE but this can only be determined at run-time. It is regarded with suspicion, and in order to assume its real type again it must face the challenge of an assignment attempt.

     w_choices: USER_PREF
     ...
     w_choices ?= retrieved (data_file)

An assignment attempt (the ?= operator) only succeeds if the source object has the same type as the target reference. More strictly, the dynamic type of the source expression must conform to the static type of the target entity.

Failure of this test is not treated as an error; the target simply becomes void. In this case, if retrieved doesn't refer to a USER_PREF object the assignment won't work and w_choices becomes void.

More flexible and selective facilities are offered by classes in the basic libraries such as ENVIRONMENT, FILE and FILE_SYSTEM, but the details are platform-dependent.

The assignment attempt is used in any situation where an object whose general type is known needs to be treated as a more specific type.

Internal

Debuggers, browsers and mixed language programming tasks require access to the internal representation of objects.

Such access is provided either by a low-level C callable interface to the Eiffel runtime system, or by basic library classes. It is not defined as part of the language.

Obviously, such power is not for casual use, and ordinary applications will not need it.

Exceptions

There are a few kinds of error which cannot reasonably be dealt with in the normal flow of control. These include hardware errors and exhaustion of resources like disk space or memory. Eiffel's exception facility provides a properly disciplined defence against such unexpected events.

The idea of 'programming by contract' underlies the use of preconditions, postconditions and the class invariant. It assigns responsibility clearly between a routine and its caller.

The caller is responsible for satisfying the precondition, and the routine is then responsible for satisfying the postcondition and maintaining the invariant.

A routine may sometimes be prevented from completing by events outside its control. In this case the routine fails and Eiffel's exception handler takes over.

The failure of a called routine causes the caller to fail too, so the exception propagates up the call stack to the root object's creation procedure. The runtime system might dump a traceback to the screen or a file to identify the point of failure.

This escalation can be intercepted at any stage by an application exception handler. Such a handler must be intimately familiar with the context of the failure; in Eiffel it is associated with the routine rather than the class or system, in the form of a 'rescue' clause.

A routine can end only by fulfilling its contract or failing. A rescue clause is part of the routine, and has only two courses of action open to it. It may correct the cause of failure and try again, or it can clean up and concede defeat.

In order to devise sensible recovery strategies in critical routines, the application might need information about the nature of the exception. Support library class EXCEPTIONS provides such information, as well as facilities to tune the exception mechanism.

Query and Command

Class STD_FILES illustrates another aspect of the Eiffel style, the division of features into queries and commands.

Feature read_line does not return a value. It is a command, intended to perform an action and modify the internal state of the object. The procedure attempts to consume characters from the standard input until the end of the current line has been seen. The attempt might succeed or fail; either way, read_line has done the job it contracted to do.

Feature last_string returns a value. The feature might be a function or an attribute - we can't tell, and it doesn't really matter to the caller. If a call to read_line has succeeded in reading a line, the value returned by last_string is that line. Otherwise, last_string returns an empty string.

Feature last_string is a query. Calling it has no permanent effect on the object. If you call last_string several times without calling read_line you get the same string each time.

On the other hand, each call to read_line reads another line (all being well). If you call read_line five times then five lines will be read, regardless of any intervening calls to last_string.

Separating commands from queries leads to classes which are easier to use. They remember their own state, so the caller doesn't have to. The logic of the caller can become more relaxed and clearer.

Conclusion

This paper has taken a tour through some features of the Eiffel language, and tried to show why they were designed and how they are used. Treatment of some topics has necessarily been brief, but the aim has been to give you an impression of what it is like to develop software in Eiffel.

If your interest has been aroused, you should read 'Object Oriented Software Construction, Second Edition' by Bertrand Meyer, published by Prentice Hall in 1997 (ISBN 0-13-629155-0). The definitive language manual is 'Eiffel - The Language' by Bertrand Meyer, published by Prentice Hall in 1992 (ISBN 0-13-247925).

This paper was originally published as a four-part series in the newsletter of the UK and Ireland Eiffel Interest Group. It has been revised and reformatted several times, recently with help from Don Ryan of the Dublin Institute of Technology. Thanks to all who have contributed.