14.2. Quick Tour

14.2.1. Hello World

Eiffel is designed to build large, reliable, object-oriented systems so that even our small greeting requires the scaffolding of a fully fledged class. To run the program, enter the code in a file called hello.e (same base name as the class, but lowercase), compile it with co-compile -o hello hello.e, and start the resulting executable hello.

class HELLO
create make
feature
   make is
      do
         print("Hello World%N")
      end
end

We have to grasp a number of concepts before understanding this program. First, Eiffel, like most object-oriented languages, talks about a system rather than a program. A system is a collection of classes (similar to a Smalltalk image although the latter is more a collection of objects with classes being special objects). To tell Eiffel where to start, we normally have to define a root class. Since our system contains only the HELLO class, this is not necessary.

Eiffel calls members of a class (attributes and methods) features. Classes mainly consist of feature definitions introduced with the keyword feature. In the example, we define a single method called make which prints the message.

This does not explain yet, how the method gets executed. When starting a system, Eiffel creates an instance of the root class, and that's where the create (or synonymously creation) statement comes in. It tells the compiler that the make method is a creation procedure (in other languages called "constructor") with no arguments which must be called when instantiating an instance of the class. Hence, when starting our "hello" application, Eiffel creates an instance of the root class HELLO and calls the constructor method make which prints the message.

Calling the constructor method make is just a convention. Any other name is syntactically just as fine. Note that, as another style convention, Eiffel always uses underscore characters to separate the parts of multi-word identifiers. Features and variables are always lowercase, classes uppercase, and constants start with an uppercase letter.

14.2.2. Variables, Arithmetic, and Control Statements

Local variables of a method are declared in advance in the optional local section of a method. Eiffel being an explicitly typed language lets us specify the type of each variable using a colon and the name of the type.

class ARITHMETIC
creation make
feature
   make is
      local
         i: INTEGER
         x: DOUBLE
      do
         i := 50
         x := 1.5 + 3 * 2.0^3 + i
         print("x=" + x.to_string + "%N")
      end
end

In the example, we use the two build-in types INTEGER and DOUBLE which correspond to C's int and double, respectively. The variables are all initialized automatically to a default value corresponding to their type. For numerical types, this is zero.

As for the variable declaration, Eiffel follows the Pascal syntax for the assignment operator (I still remember how unintuitive C's use of equal operator for assignment appeared to me when moving from Pascal/Modula to C). Arithmetical expression work as expected including the correct preferences, automatic conversion from integer to double, and the power operator ^.

There are a few details in the print statement which we have not seen before. First, the statement prints three strings which are concatenated with the + operator demonstrating Eiffel's ability to use operators not just for numerical types. Second, we convert the floating point number x to a string using the to_string feature. Eiffel uses, like many other object-oriented languages, the dot notation to refer to features of objects. For methods without parameters, we can omit the empty parameter list so that the call looks just like the access to an attribute. Using parentheses in this case will result in a compiler warning.

The statements are not ended or separated by any special character. You only need to use a semicolon if you try and put multiple statements in a single line. Here is the packed version of the program above.

class ARITHMETIC_PACKED creation make
feature
   make is
      local i: INTEGER; x: DOUBLE
      do
         i := 50; x := 1.5 + 3 * 2.0^3 + i
         print("x=" + x.to_string + "%N")
      end
end

Eiffel also has a boolean type and supports the usual boolean operators (using their proper names, not C's symbols).

class ARITHMETIC
creation make
feature
   make is
      local
         x: DOUBLE
         b: BOOLEAN
      do
         x := 100
         b := x > 10 or x /= 50 and not ("blub" <= "blah")
         print("b=" + b.to_string + "%N")
      end
end

Here, /= is obviously not the devide-and-update operator used in the C family, but the unequal sign. Also note that the parentheses around the string comparison are required, because the not binds stronger than the comparison operators.

Next to arithmetic, we have usually covered functions, which, in a purely object-oriented language such as Eiffel, means a method returning a value.

class FUNCTION_EXAMPLE
creation make
feature
   make is
      do
         print("result=" + times_square(2, 3).to_string + "%N")
      end

   times_square(x: DOUBLE; i: INTEGER): DOUBLE is
      do
         Result := i * x
         Result := Result * Result
      end
end

Parameter and return types are specified like the types of local variables. The semicolon separating the two arguments is only needed if they are defined on the same line. The return value is defined using the implicit variable Result. As you can see, it can be used like any other variable. When the function is left, the value of this variable is returned to the calling routine.

Do not try to assign a value to a formal parameter of a method. In contrast to the C family, Eiffel does not allow this (mostly confusing) practice.

Eiffel restricts itself to a relatively small set of control statements, the usual if-then-else, a case statement, and a loop instruction that corresponds semantically to C's for loop. In all three cases, the syntax is straight forward.

class IF_EXAMPLE
creation
   make
feature
   make is
      do
         compare(4, 5)
      end

   compare(x: INTEGER; y: INTEGER) is
      do
         if x < y then
            print("less")
         elseif x = y then
            print("equal")
         else
            print("greater")
         end
      end
end

The case statement is called inspect in Eiffel, but otherwise works as expected. The inspected expression (and thus all the expressions it is checked agains) must be an integer or a character.

class INSPECT_EXAMPLE
creation
   make
feature
   make is
      do
         print("2=" + to_string(2))
      end

   to_string(x: INTEGER) : STRING is
      do
         inspect x
         when 1 then Result := "one"
         when 2 then Result := "two"
         when 3 then Result := "three"
         else Result := "another"
         end
      end
end

The loop instruction can be viewed as a readable version of C's for statement. You define initialization instructions, an exit condition, and the body of the loop which is executed until the exit condition becomes true. In Section 14.2.4> we will cover the possibility to add invariants and variants to loops as part of the Design by Contract.

class LOOP_EXAMPLE
creation
   make
feature
   make is
      local
         i: INTEGER
      do
         from
            i := 1
         until
            i > 3
         loop
            print("i=" + i.to_string + "%N")
            i := i + 1
         end
      end
end

14.2.3. Classes and Features

We had to define classes from the very beginning (even for our "Hello World" program), but for now we have used them merely to set the context for functions doing the rest. Let us now define our standard class example, a person, in Eiffel.

class PERSON
creation
   make
feature
   make(a_name: STRING, an_age: INTEGER) is
      do
         name := a_name
         age := an_age
      end
   
   to_string: STRING is
      do
         Result := "name=" + name + ", age=" + age.to_string
      end;
feature
   name: STRING
   age: INTEGER
end

The only new element are the two attributes name and age. They can be used inside the class like any other variable. Since we have not restricted the access to these features, they are also readable from any other class. However, you can not set an attribute from the outside, since this could cause an inconsistent state of the object. Eiffel does not know the concept of public read-write attributes (which is not good style in the languages supporting it). If you want other classes to be able to change an attribute, you have to define a setter method for it.

   set_name(a_name: STRING) is
      do
         name := a_name
      end

Also note that Eiffel does not allow us to use the same name for two different features of a class even if they have different signatures. In Eiffel, a feature name is always unique. If we would like two different constructor methods, we have to give them different names.

Having defined the PERSON class, we would like to create person objects. Using Eiffel, objects spring into life with a bang (actually two).

class TEST creation make
feature
   make is
      local
         person: PERSON;      
      do
         !!person.make("Homer", 55)
         print("person=" + person.to_string + "%N")
         print("name=" + person.name + "%N")
      end
end

If you prefer a more readable syntax, you can also use the keyword create instead.

         create person.make("Homer", 55)

The variable person is a reference to an object of the class PERSON. At the beginning of the method, this reference is set to Void. We can easily verify this in the code using an assertion:

         check person = Void end

The predefined constant Void corresponds to a null pointer just like Pascal's nil or Java's null. In contrast to most other object-oriented languages, we do not create an object (for example, using some factory method such a new) and assign it to a variable. Instead, Eiffel performs both in one step with the create or "bang bang" instruction applied to the variable.

Next, let's again derive an employee class that adds an employee number to a person.

class EMPLOYEE inherit
   PERSON
      rename make as person_make
      redefine to_string
      end
creation
   make
feature
   make(a_name: STRING; an_age, a_number: INTEGER) is
      do
         person_make(a_name, an_age)
         number := a_number
      end
   
   to_string: STRING is
      do
         Result := Precursor + ", number=" + number.to_string
      end;
feature
   number: INTEGER
end

The first striking element is the extensive declaration of the inheritance in the inherit clause. Since we would like to define a new constructor taking the employee number as an addition argument, we have to hide the original make feature of the PERSON class by renaming it to person_make (remember that feature names must be unique).

We would also like to provide a new implementation of the to_string method so that the employee number gets printed as well. To avoid simple mistakes such as an incorrect spelling of the redefined method, we must state our intention explicitly using the redefine instruction.

Once we have prepared the class in this manner, the implementation is straight-forward. In the new constructor make we can call the old one using its new name person_make. Similarly, the special name Precursor refers to the implementation of the current method in the parent class. This is used in the redefinition of the to_string method to add the employee number to the string provided by the PERSON class.

All strongly typed object-oriented languages let us declare abstract methods, that is, methods which rely on subclasses to provide an implementation. In Eiffel, we do not talk about abstract methods, but deferred features. Here is an example defining the interface for an account with the minimal balance, deposit, withdraw functionality.

deferred class ACCOUNT feature
   balance: DOUBLE is
      deferred
      end
   deposit(amount: DOUBLE) is
      deferred
      end
   withdraw(amount: DOUBLE) is
      deferred
      end
end

Replacing the do block by the keyword deferred makes the features deferred. A class with at least one deferred feature is a deferred class and has to be marked as such.

Features without arguments can be implemented as methods or as attributes (that's why the more general term "deferred feature" makes sense). Here is probably the simplest implementation of the account interface.

class SIMPLE_ACCOUNT inherit
   ACCOUNT
      redefine balance, deposit, withdraw end
feature
   balance: DOUBLE

   deposit(amount: DOUBLE) is
      do
         balance := balance + amount
      end

   withdraw(amount: DOUBLE) is
      do
         balance := balance - amount
      end
end

Implementing a deferred class works just like inheriting from any other class. In the redefine clause, we tell the compiler which features we are going to implement. In this implementation of the account, the balance feature is implemented as an attribute.

Of course, deferred classes can not be instantiated. But now that we have an implementation, we can use the class in a test program.

class TEST creation make
feature
   make is
      local
         account: ACCOUNT
      do
         !SIMPLE_ACCOUNT!account

         account.deposit(10.0)
         account.withdraw(5.0)
         print("balance=" + account.balance.to_string + "%N")
      end
end

If you did not like the "bang bang" syntax for object creation, you won't like special case for derived classes either. The concrete class to be instantiated is put between the two quotation marks of the object creation instruction.

14.2.4. Design by Contract

As mentioned in the introduction, Design by Contract sets Eiffel apart from other languages. When we look at a library, we want to know how to call a function and what a function does. The first question is answered by the function's signature. It tells us which parameters the function expects, and, in strongly typed languages, which type the supplied arguments must have. The semantics of the function, however, are normally described in comments only.

Eiffel goes one step further by giving us the means to specify some semantic information in the code. We can define semantic conditions on the input (preconditions), output (postconditions), and state of the object (invariants). Here is an example:

class ACCOUNT
create make
feature
   make(a_minimal_balance: DOUBLE; initial_balance: DOUBLE) is
      require
         consistent_balance: a_minimal_balance <= initial_balance
      do
         minimal_balance := a_minimal_balance
         balance := initial_balance
      ensure
         balance_set: balance = initial_balance
         minimal_balance_set: minimal_balance = a_minimal_balance
      end

   deposit(amount: DOUBLE) is
      require
         positive_amount: amount > 0
      do
         balance := balance + amount
      ensure
         balance_updated: balance = old balance + amount
      end
   
   withdraw(amount: DOUBLE) is
      require
         positive_amount: amount > 0
         enough_money: balance - amount >= minimal_balance
      do
         balance := balance - amount
      ensure
         balance_updated: balance = old balance - amount
      end
   
feature -- attributes
   minimal_balance: DOUBLE
   balance: DOUBLE
   
invariant
   balance_ok: balance >= minimal_balance
end

The example defines an account with a constructor and the two methods deposit and withdraw. Here is a test program using the account class.

class TEST
create make
feature
   make is
      local
         account: ACCOUNT
      do
         !!account.make(-1000, 0)
         account.deposit(50)
         account.withdraw(150)
         print("balance=" + account.balance.to_string + "%N")
      end
end

The interesting part is obviously not the minimal implementation of the method, but way the class and its method are adorned with conditions which ensure that the class works as expected. The constructor takes two arguments, a minimal and an initial balance. These arguments only make sense if the initial balance is not less than the minimal balance. Hence, we define a precondition in the require section of the method which checks exactly that. The purpose of the constructor is to set the attributes to the given values. This result expected by the client calling the method is verified using a postcondition in the ensure section of the method. Similarly, we define pre- and postconditions for the two other methods. The precondition makes sure that we never get below the minimal balance, and the postcondition check that the balance has been updated correctly. The nice syntactical old feature lets us refer to the value of the balance before the method is executed.

Finally, there is the invariant section of the class which lets us define conditions which have to be fulfilled by an object of the class at any time. In our case, we make sure that the balance never gets below the minimal balance.

These three elements, preconditions, postconditions, and invariants are at the heart of Eiffel's design by contract. I hope that even this simple example gives you an idea how much semantic information can be captured with these language constructs.

Eiffel also lets us add additional checks to the program flow. The simplest one is the check instruction which can be placed anywhere to check a condition (like C's assert).

class CHECK_EXAMPLE
creation make
feature
   make is
      local
         n: INTEGER
      do
         n := 5
         check
            is_five: n = 5
            is_positive: n > 0
         end
         print("checks succeeded")
      end
end

As already mentioned in Section 14.2.2>, it is also possible to add special checks to loops which help to prevent common errors such as infinite loops. Here is a program computing the greatest common divisor of two integers using Euclid's algorithm.

class GCD
creation make
feature
   make is
      do
         print("gcd(25, 35)=" + gcd(25, 35).to_string + "%N")
      end

   gcd(a, b: INTEGER): INTEGER is
      require
         a > 0
	 b > 0
      local
         x, y: INTEGER
      do
         from
            x := a
            y := b
         invariant
            x > 0
            y > 0
         variant
            x.max(y)
         until
            x = y
         loop
            if x > y then
               x := x - y
            else
               y := y - x
            end
         end
         Result := x
      end
end

A loop invariant is a condition which must be true during the whole iteration. In the example, the two variables x and y must stay positive. The variant of a loop is an integer expression which is always positive and becomes smaller from iteration to iteration. This way we can guarantee that the loop will end. In the Euclidian algorithm, we know that the maximum of x and y is a good candidate for a loop variant.

You may raise at least two questions at this point: What happens if one of the conditions is violated and what is the performance impact of all these checks? To answer the first question, let's try to create an account with an invalid balance by calling !!account.make(100, 10).

*** Error at Run Time ***: Require Assertion Violated.
*** Error at Run Time ***: consistent_balance
3 frames in current stack.
=====  Bottom of run-time stack  =====
System root.
Current = TEST#0x8061a60
line 4 column 2 file ./test.e
======================================
make TEST
Current = TEST#0x8061a60
account = Void
line 8 column 4 file ./test.e
======================================
make ACCOUNT
Current = ACCOUNT#0x8061a88
        [ minimal_balance = 0.000000
          balance = 0.000000
        ]
a_minimal_balance = 100.000000
initial_balance = 10.000000
line 7 column 42 file ./account.e
=====   Top of run-time stack    =====
*** Error at Run Time ***: Require Assertion Violated.
*** Error at Run Time ***: consistent_balance

That's what I call a comprehensive error description. Not only do we get the name of the violated condition and the values of the parameters passed to the constructor method, but also the state of the account object in question. Here is an example for the violation of a loop variant. Assume that we forget the update of the loop variable.

class LOOP_EXAMPLE
creation make
feature
   make is
      local
         i, n: INTEGER
      do
         n := 3
         from
            i := 1
         invariant
            i > 0
         variant
            n - i
         until
            i > n
         loop
            print("i=" + i.to_string + "%N")
         end
      end
end

Running this program results in the following error message.

i=1
*** Error at Run Time ***: Bad loop variant.
Loop body counter = 1 (done)
Previous Variant = 2
New Variant = 2

2 frames in current stack.
=====  Bottom of run-time stack  =====
System root.
Current = LOOP_EXAMPLE#0x8061ab8
line 4 column 4 file ./loop_example.e
======================================
make LOOP_EXAMPLE
Current = LOOP_EXAMPLE#0x8061ab8
i = 1
n = 3
line 14 column 15 file ./loop_example.e
=====   Top of run-time stack    =====
*** Error at Run Time ***: Bad loop variant.
Loop body counter = 1 (done)
Previous Variant = 2
New Variant = 2

Again, the error message points precisely at the problem.

How do all these assertion impact the performance? Eiffel allows to switch the checks on or off without changing the source code. This way, we can decide on a case by case basis whether the performance hit for the evaluation of the conditions is justified or not.

14.2.5. Visibility

All features we have defined for now are public, that is, they can be accessed by any other class. However, Eiffel allows us to restrict the visibility of features. Eiffel does not use fixed visibility modifiers (e.g., private, protected, public). Instead, we can specify which classes are allowed to see a group of features. Together with the special classes ANY and NONE, we can express private, protected and public visibility, but have more freedom to give other classes access as well. Here is a simple example.

class COUNTER
feature {ANY}
   increment: INTEGER is
      do
         count := count + 1
         Result := count
      end
feature {COUNTER}
   reset is
      do
         count := 0
      end
feature {NONE}
   count: INTEGER
end

To restrict the visibility of a feature block, we add the names of the classes which are allowed to see the features in braces. The visibility always includes all subclasses of the specified classes. Since all application classes derive from ANY, the first feature increment is public. The reset feature is visible by the class COUNTER itself and all its children (protected feature in other languages). Finally the last feature count is restricted to the class NONE. As the name suggests, no other class can be derived from this special class, which makes the feature private.

By default, features are public. The feature definitions in the previous sections are just shortcuts for feature {ANY}. Besides the basic visibility rules demontrates above, we can also selectively give other classes access to certain features (similar to the friend mechanism in C++).