Eiffel offers a number of unique or at least unusual programming features, with an own, radical view on - for example - imperative code, object-orientation or software correctness. This makes it a very interesting programming language to study, at least for a view beyond the current C/C++/Java hegemony.

This is not a tutorial for Eiffel. It is also not a motivation to start getting interested in Eiffel or to advertise all the amazing features this language has. Instead, it is a quick primer to someone who would like to read Eiffel code and get a feel for the language, assuming an understanding in Java or a similar language (C#, Go, etc.).

Basics

Eiffel is a statically and strongly typed object-oriented programming language. It uses automatic memory management with a garbage collector.

Compared with Java, Eiffel is significantly older. It was invented around the second half of the 1980s, make it roughly as old as C++. Eiffel does not use byte-code, instead it is compiled to machine-code, normally via C as an intermediate language.

The main target for Eiffel is writing large, complex systems with thousands to million lines of code. Most of the features in Eiffel concentrate on this, and often shelve small syntactic shortcuts for long term readability and consistency.

Available Compilers

Currently, three Eiffel compilers are maintained actively. All of them are available as open-source software.

The reference compiler implementation is EiffelStudio. This is the original compiler from Bertrand Meyer’s company Eiffel Software (formerly known as ISE). It is the main compiler I am using and probably the version you should start with if you interested in developing in Eiffel.

As the name suggests, EiffelStudio comes with a fully integrated IDE, offering a lot of useful tools for software development. Eiffel is a statically typed programming language, and a development environment understanding the language can offer many useful features like code completion and navigation.

EiffelStudio is dual licensed. There is a commercial version that you can buy, or you can use the GPLed open-source version. EiffelStudio is available on many platforms (even platforms like BSD or Solaris), but the main target platforms are Windows and Linux.

As a second Eiffel compiler, you can use the Gobo Eiffel Compiler. GOBO is a large library and toolset to be used with EiffelStudio, but this library also contains a full Eiffel parser that is very well maintained and fast. The library also comes with a usable backend. Together, they allow the compilation of Eiffel binaries and the bootstrapping of GOBO itself (that means that no EiffelStudio is needed to use GOBO). On the downside, all tools are purely command line, and there are some vital features missing from the backend (like runtime checking of contract). GOBO is licensed under the Eiffel Forum License, a software license that is similar to the BSD license and used by many other Eiffel libraries.

The third compiler is GNU Liberty Eiffel. This compiler was originally developed by INRIA in France under the name Small Eiffel. When it grew larger, the prefix “Small” did not seem to be adequate anymore, and was renamed to SmartEiffel. After the code base had been abandoned for a while, development has recently take up speed again under the name Liberty Eiffel.

While EiffelStudio and GOBO are mostly compatible with each other, Liberty Eiffel has departed from the recent developments. So, code written for Liberty Eiffel normally does not work with EiffelStudio and GOBO (and vice versa). But the Liberty Eiffel team is actively trying to improve compatibility between the compilers.

Terminology

Eiffel is roughly as old as C++. Because of this (and perhaps it was developed by a frenchman), it has not been subject to the current hegemony of terminology introduced by C and C++, like static, const, final, private or public. There are a number of words used in Eiffel that are uncommon to a Java developer. Some are really just one-to-one translations, but some other words also carry different connotations.

Here are some of the words commonly used in Eiffel. Java equivalents are provided in italics.

  • Void: null
  • deferred: abstract
  • frozen: final (on a class, so it can not be subtyped)
  • expanded: Does not exist in Java, it means that instanced do not allocate memory on the heap and are referenced, but instead are allocated on the stack (for local variables and arguments), or are inlined into other objects.
  • feature: Classes are made up of features, so the term is used for methods and fields.
  • command: A feature that does not yield a value (return type void in Java).
  • query: A feature that return a value. This also includes fields.
  • inherit: A super-subclass relation, similar to extends.
  • redefine: override an inherited feature.
  • agent: A functional closure, so a deferred feature invocation with some arguments already bound, and some still open.
  • inline agent: An agent with the code provided locally, so similar to a lambda (->) expression in Java 8.
  • ANY: This is the top-most type in Eiffel, similar to the Object class in Java. If a class has not explicit super-class, it is a sub-class of ANY.
  • NONE: This has no equivalent to Java, it is a common sub-type of all classes. So, all types in Eiffel for a mathematical lattice, with ANY as the top and NONE as the bottom element. Void is the only instance of NONE.
  • attached type: A recently new development implements Void-safety into the language, so the ability to detect potential null-pointer dereferencing at compile time. The opposite of attached is detachable type.
  • separate: An other type construct referencing an object on a foreign processor in the SCOOP programming concurrency model (see below).

A First Program

Here is a small example written in Eiffel to illustrate some major concepts.

class BOTTLES

create
  make

feature -- Make

  make
      -- Initialization
    local
      i: INTEGER
    do
      from
        i := 99
      until
        i < 1
      loop
        print (bottle_text(i))
        print (" of beer on the wall, ")
        print (bottle_text(i))
        print (" of beer.%N")
        print ("You take one down, you pass it around.%N")
        i := i - 1
        print (bottle_text(i))
        print (" of beer on the wall.%N%N")
      variant
        i
      end
    end
	
feature -- Word support

  bottle_text (i: INTEGER): STRING
      -- String containing `i' and the correct version of "bottle" or "bottles".
    require
      positive: i >= 0
    do
      if i > 0 then
        Result := i.out + " bottle"
        if i > 1 then
          Result.append_character('s')
        end
      else
        Result := "No more bottles"
      end
    end
end

Eiffel does not have static methods, so there is no main method that is used to start a program. Instead, an instance of a specific class, the root class, is created. The program terminates once the constructor of this instance returns.

In Eiffel, class names are all caps, in this case the root class is called BOTTLES. In Java, the name of the constructor is the same as the name of the class. In Eiffel, any feature can be used as constructor, as long as its name is listed in the create expression. Most of the time, the name of constructor feature use the word make as a prefix (make, make_empty or make_duplicate).

Eiffel has no overloading; there is always at most a single feature with a given name.

Comments start with two dashes (–) and go on until the end of the line. This is similar to SQL, or the two slashes in Java (// …). Eiffel has not concept of block comments like Java (/* … */).

Local variables are declared at the beginning of a feature, in the local part.

Eiffel is a verbose language, in the tradition of Algol, ADA or Pascal. It uses keywords instead of symbols. Code blocks normally begin with some kind of keyboard and end with the keyword end.

There is only a single loop construct in Eiffel, the from loop. It normally loops until an until condition is satisfied (there is a special across keyword for iterators). Unusual for programming languages, it also allows the specification of a loop invariant and variant, which can be helpful to determine if the loop is correct and will terminate.

The if then else end construct is more common, the only unusual keyword is elseif to be used for a chained case distinction.

print is available everywhere (inherited from ANY), and will print any value to standard out (it will call the out feature to get a STRING value, similar to toString() in Java).

Special characters are not introduced by a backslash, but by a percentage sign. %N stands for the newline character.

Assignments use :=, the normal = is used to check if to values are the same (reference equality). The equivalent to equals for value equality is is_equal.

Using a semicolon (;) between calls is possible, but not required. There are some corner cases where it is needed, but normally you do not need semicolons for sequential composition, even if you write multiple command on the same line.

The feature keyword can be used at any time between features (but does not need to be there, except before the first feature definition). It is used to categorise features.

require is part of the pre-conditon of Design by Contract (see below).

There is no return keyword in Eiffel. Instead, functions have an implicit local variable Result which takes the return value. Whatever its value is at the end of the function is the return value.

Most objects in Eiffel are mutable, this includes strings. So, STRING in Eiffel is more like StringBuffer in Java. A literal string ("Hello") creates a new instance of STRING every time it is executed.

Inheritance

Eiffel offers multiple inheritance, but very differently from C++. Multiple-inheritance in Eiffel works reasonably well and - together with Design by Contract - is a great tool to model and decompose complex systems.

Different from all other programming languages, inheritance is a complex relationship. Here is an example from one of the base data-types:

class ARRAYED_SET [G] 

inherit
  LINEAR_SUBSET [G]
    undefine
      prune_all, copy, is_equal, fill
    select
      extend, put, prune
    end
    
  ARRAYED_LIST [G]
    rename
      extend as al_extend,
      put as al_put,
      prune as al_prune
    export
      {ANY} valid_cursor_index, readable, writable, to_array, new_cursor
      {ARRAYED_SET} go_to, area, area_v2, cursor, full, i_th, lower, 
        valid_cursor
      {NONE} all
    undefine
      is_inserted,
      changeable_comparison_criterion
    end

We can see that ARRAYED_SET is generic (takes a type argument called G) and inherits from two classes, LINEAR_SUBSET and ARRAYED_LIST.

As we do not have overloading in Eiffel, the name of a feature is critical to define its identity. If both parents implement a feature with the same name, then we have to indicate which code to throw away. We do this with the undefine keyword. This also indicates which features are overridden locally.

The export keyword changes the visibility of certain features to callers (see below).

An interesting keyword is rename, allowing you to change the naming of a parent feature in a sub-class. It is interesting for inheritance, allowing you separate features that have the same name in two different parents, or merge features that have different names in parents. It is also interesting for modelling, allowing you to use specialised terminology in a subclass (think the general buttons in INPUT_DEVICE, but keys in KEYBOARD).

Visibility

There is no private, public or protected in Eiffel.

private just does not exist. No feature is ever hidden from subtypes, every feature can be accessed and redefined (except when marked as frozen, but that is a different story).

In Java you can write a.b = c, changing b in a, assuming that b is public. This is called remote assignment, as you are assigning to an attribute in another object.

There is no remote assignment to attributes in Eiffel, to change an attribute in another object you always have to go through a settor. It is like attributes are public for reading, but private for writing.

Every feature is public, but as there is no remote assignment, attributes become “read-only from the outside”.

But it is possible to restrict visibility of a feature to a specific class and all its children. This is somewhat in the direction of the protected keyword in Java, but more refined as you can specify the caller explicitly and are not bound to concepts like packages.

feature {X} -- Internal calls

  foo
    do
      ..
    end

In this example, calling foo is restricted to instances of type X and its descendants. Libraries often have an empty LIBRARY_INTERNAL (or similar named) class for accessing internal data structures, and classes inherit from this class to gain access to these features.

Design by Contract

Design by contract (DbC) is perhaps the most well-known feature of the Eiffel language and makes it stand out among its peers. There are some languages that have adopted it from the start (for example D), and many libraries have been written to add DbC to existing languages like Java or C#.

Unfortunately, DbC is a language feature that is very hard to add after-the-fact, and Eiffel is probably the only language where DbC is lived by the whole community.

DbC means adding certain assertions to the language. The three most important ones preconditions (require), postconditions (ensure) and class invariants (invariant).

In contrast to Java assertions, these are not part of the body of a feature (method implementation), but instead become part of the interface of a feature.

Being part of the interface, they are inherited from parent to child classes, even if the implementation is overridden/redefined by the child class.

Here is a small example:

class PERSON

feature -- Access

  age: INTEGER
    -- Age of a person in years

  father: PERSON
    -- Father of the person

feature -- Settors

  set_father (a_person: PERSON)
      -- Set `father' to `a_father'.
    require
      older: a_person.father.age > age
    do
      father := a_person
    ensure
      father_set: father = a_person
    end
    
invariant
  father_older: father.age > age
end

The invariant clause declares that the father of a person always needs to be older than the person itself. The require demands that changing the father requires you to provide a person old enough, and the ensure tell you that set_person sets the father to the person provided.

Any subclass of PERSON will need to implement this, even if the implementation of set_father is changed.

Style

The Eiffel community is rather strict about style, and there are a few principles that are followed by every Eiffel library even though they are not demanded by the language or compiler.

Command/Query Separation demands that queries (functions with return values) are always free of visible side-effects. In reality, certain side-effect like caching is acceptable.

For example if a Java-like language has a get_character() function to retrieve the next character from an input stream, Eiffel would always have a read_character command to read a character and make the result available through a last_character query.

Based on this, features follow is strict naming policy: Commands require a verb in them (write_table instead of table_to_disc), while queries must avoid verbs (parents instead of retrieve_parents). This is specially true for accessors which in Java traditionally have a get in front (get_father) would just be called father in Eiffel.

Boolean queries are an exception to this, and are normally names with prefixes like is or has (is_empty, has_single_maximum).

Option/Operand Separation demands minimal number of arguments to features. Instead, good defaults should be given for optional arguments, and they should be set individually with settors. So, instead of having a single feature print_text (a_text: STRING, a_bold: BOOLEAN), Eiffel prefers to have print_text (a_text: STRING), plus two features enable_bold and disable_bold (or a single set_bold (a_bold: BOOLEAN)).

Concurrency

Regular Eiffel does not offer a lot of support around multi-threading. In contrary, multi-threading in Eiffel is much harder than in many other programming languages, as Eiffel make heavy use of imperative code and mutable state. Design-by-Contract can make very little guarantees when multiple threads operated on the same objects at the same time.

There is a multi-threading library available for Eiffel, but it should be used very carefully. There be dragons.

As an alternative for multi-threading, Eiffel has for a very long time suggested a concurrency model based on so called separate objects and message passing. This model is called SCOOP. For a long time, SCOOP was mostly academic, but in recent years, support for SCOOP has been slowly introduced into the language. I have not yet used it,