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,