allanswers.org - comp.lang.eiffel Frequently Asked Questions (FAQ)

 Home >  Programming >

comp.lang.eiffel Frequently Asked Questions (FAQ)

Section 2 of 2 - Prev - Next


non-expanded basic types (INTEGER_REF, REAL_REF, etc), POINTER
type to enable external references to be passed around, call to
external routines no longer implicitly pass the current
object as the first parameter.

Since then the following change has been adopted and widely
implemented:

   - The Precursor construct allows the ancestor's version of
     a redefined feature to be conveniently called (see LPAR).

   - A keyword-based notation (create/creation) for object creation 
     was introduced as an alternative to the "!!" notation.
	 
Bertrand Meyer is currently working on Eiffel: The Language,
third edition, which will describe a significantly updated version
of the language, known as 'Eiffel 5'. Some of the constructs being
introduced, like Agents (routines as a first class construct),
have already found their way into Eiffel Software's implementation.

The draft for this next edition is reachable from Bertand Meyer's 
home page at http://www.inf.ethz.ch/~meyer/publications/

 --------------------

LLIB: What libraries come with Eiffel?

All vendors aim to support the Eiffel Library Standard kernel classes.

In addition, extensive library classes are supplied with the compilers
including data structures, graphics, lexical analysis and parsing, IO,
persistence, formatting, GUI and more.

Many libraries are provided by third parties, mostly as open source
code. There are too many to list here. A good starting point is at
http://www.cetus-links.org/oo_eiffel_libraries.html

 --------------------

LDBC: What's the big deal about preconditions and postconditions?

The big deal is that it supports programming by contract. For example,
preconditions (require clauses) are simple boolean statements that are
used to check that the input arguments are valid and that the object
is in a reasonable state to do the requested operation. If not, an
exception is generated. Similarly, postconditions (ensure clauses)
make sure that a method has successfully performed its duties, thus
"fulfilling its contract" with the caller. Invariants are boolean
expressions that are checked every time an object method returns back
to a separate object.

You can use these ideas in any OO programming language, but usually
must supply your own assertion mechanisms or rely on programmer
discipline. In Eiffel, the ideas are integrated into the whole fabric
of the environment. We find them used by:

 - the exception handling mechanism.
   (Tracebacks almost always identify the correct culprit code since
   preconditions almost always denote an error in the calling method,
   while postconditions denote an error in the called method.)

 - the automatic compilation system.
   (Assertions can be disabled entirely or selectively by type on a
   per class basis.)

 - the Eiffel compiler
   (Invariants, preconditions and postconditions are all inherited in
   a manner that makes logical sense.)
   (Assertion expressions are not allowed to produce side effects so
   they can be omitted without effect.)

 - the automatic documentation tools
   (Preconditions and postconditions are important statements about
   what a method does, often effectively describing the "contract"
   between the caller and callee. Invariants can yield information
   about legal states an object can have.)

In the future we expect to see formal methods technology work its way
into the assertion capability. This will allow progressively more
powerful constraints to be put into place. In addition, Meyer has
argued in his concurrency model (see LTSK) that assertions play
a central role in concurrent and distributed object-oriented
programming.

 --------------------

LCQS: What is command/query separation?

It is a convention in Eiffel that functions (routines
that return something) must not have side effects. This
means all routines changing the state of an object should
be procedures, not functions. If they return a result,
a helper attribute can be used.

For instance, a typical Eiffel class representing a
facility to read a file will offer a routine to read
a line, and make the result available separately, so 
a client will do:

  a_file.read_line -- read_line is a procedure
  do_something_with (a_file.last_string)

This is essential for design by contract, because assertions
are boolean expressions that must not change the state of the
objects, otherwise the program will behave differently when
assertions are checked.

Internal side effects, that change the concrete but not
the abstract state of the object and are not visible from
outside are acceptable.

 --------------------

LCON: Please explain and discuss covariance vs. contravariance.

Consider the following situation: we have two classes PARENT and
CHILD. CHILD inherits from PARENT, and redefines PARENT's feature
'foo'.

   class PARENT
      feature
         foo (arg: A) is ...
   end

   class CHILD
      inherit
         PARENT redefine foo end
      feature
         foo (arg: B) is ...
   end

The question is: what restrictions are placed on the type of argument
to 'foo', that is 'A' and 'B'? (If they are the same, there is no
problem.)

Here are two possibilities:

   (1)  B must be a child of A (the covariant rule - so named because
        in the child class the types of arguments in redefined
        routines are children of types in the parent's routine, so the
        inheritance "varies" for both in the same direction)

   (2)  B must be a parent of A (the contravariant rule)

Eiffel uses the covariant rule.

At first, the contravariant rule seems theoretically appealing. Recall
that polymorphism means that an attribute can hold not only objects of
its declared type, but also of any descendant (child) type. Dynamic
binding means that a feature call on an attribute will trigger the
corresponding feature call for the *actual* type of the object, which
may be a descendant of the declared type of the attribute. With
contravariance, we can assign an object of descendant type to an
attribute, and all feature calls will still work because the
descendant can cope with feature arguments at least as general as
those of the ancestor. In fact, the descendant object is in every way
also a fully-valid instance of the ancestor object: we are using
inheritance to implement subtyping.

However, in programming real-world applications we frequently need to
specialize related classes jointly.

Here is an example, where PLOT_3D inherits from PLOT, and
DATA_SAMPLE_3D inherits from DATA_SAMPLE.

   class PLOT
      feature
         add(arg: DATA_SAMPLE) is ...

   class PLOT_3D
      inherit
         PLOT redefine add end
      feature
         add(arg: DATA_SAMPLE_3D) is ...

This requires the covariant rule, and works well in Eiffel.

It would fail if we were to put a PLOT_3D object into a PLOT attribute
and try to add a DATA_SAMPLE to it. It fails because we have used
inheritance to implement code re-use rather than subtyping, but have
called a feature of the ancestor class on an object of the descendant
class as if the descendant object were a true subtype. It is the
compiler's job to detect and reject this error, to avoid the
possibility of a run-time type error.

Here's another example where a real-world situation suggests a
covariant solution. Herbivores eat plants. Cows are herbivores. Grass
is a plant. Cows eat grass but not other plants.

   class HERBIVORE                               class PLANT
   feature
      eat(food: PLANT) is ...
      diet: LIST[PLANT]

   class COW                                     class GRASS
   inherit                                       inherit
      HERBIVORE                                     PLANT
         redefine eat
      end
   feature eat(food: GRASS) is ...

This does what we want. The compiler must stop us from putting a COW
object into a HERBIVORE attribute and trying to feed it a PLANT, but
we shouldn't be trying to do this anyway.

Also consider the container 'diet'. We are not forced to redefine this
feature in descendant classes, because with covariant redefinition of
the argument to 'eat', the feature 'diet' can always contain any
object that can be eaten (e.g. grass for a cow). (With contravariant
redefinition of the argument to 'eat', it would be necessary to
re-open the parent class to make the type of the container 'diet' more
general).

To summarise: Real-world problems often lend themselves to covariant
solutions. Eiffel handles these well. Incorrect programs in the
presence of covariant argument redefinition can cause run-time type
errors unless the compiler catches these.

 --------------------

LCAT: Is it true that there are "holes" in the Eiffel type system?

Eiffel was designed to make it possible to catch all type errors at
compile time, so that an Eiffel program could not abort with a run time
type error.

However, there are some complex cases where the type checking
is difficult. The solution in Eiffel the Language, system level
validity checking, requires a global analysis of the whole system,
which has proven too complex and too impractical to implement.

Object Oriented Software Construction, second edition, offers a new
simpler way to check for those errors that may, if refined, provide
effective type checking but it has been questionned whether it is
too drastic so that it will make many common patterns invalid.

The main system-level type errors are:
 - restriction of exports in a descendant class.
 - covariant redefinition of routines parameters as in question LCON.
 - covariant signatures in conforming types of a generic class
   (like 'put' in LIST[ANY] and LIST[STRING]).
 - creation of redefined anchor types.
 - more obscure cases like selection of a feature that returns a
   precursor type in a multiple inheritance hierarchy, or
   indirect assignment of references to an expanded ancestor.

No compiler currently available fully implements these checks and
behaviour in those cases ranges from run-time type errors to system
crashes.

A comprehensive description of these issues and proposed solutions 
is described in this paper:
 http://www.inf.ethz.ch/~meyer/ongoing/covariance/recast.pdf

 --------------------

LTSK: Is there support for concurrency in Eiffel?

Eiffel supports concurrency in the latest specification of the
language. The SCOOP (Simple Concurrent Object-Oriented Programming)
model is described in chapter 30 of the book in "Object Oriented
Software Construction 2nd edition" by Bertrand Meyer. Papers are
also available at
 http://www.eiffel.com/doc/manuals/technology/concurrency/

Several researchers and vendors are working towards actual
implementations of SCOOP.

In the meantime, most compilers also support less safe forms 
of concurrency, like multithreading, independently of SCOOP.

 --------------------

LOVL: Why doesn't Eiffel allow function overloading?

In Eiffel, no two features of a class may have the same identifier,
regardless of their respective signatures.  This prevents the use of
function overloading, a common programming technique in languages
like C++.

Eiffel is designed to be minimal: it includes exactly the features
that its designer considered necessary, and nothing else.

Because Eiffel already supports (single) polymorphism through its
inheritance system, the only positive thing that function overloading
buys you is reducing the number of feature names, at the expense of
reducing the ability of the compiler to detect (type) errors.

Readability is also enhanced when overloading is not possible. With
overloading you would need to consider the type of the arguments as
well as the type of the target before you can work out which feature
is called. With multiple inheritance and dynamic binding this is
awkward for a compiler and error-prone for a human.

Having said that, the lack of overloading may force us to write some
common mathematical operations (e.g. matrix math) in an awkward
way, and some basic arithmetic expressions are treated specially
(the "arithmetic balancing rule", ETL p385).

 --------------------

LINC: Why is there no increment operator?

In C-like languages, there is an operator used to increment 
integer types (++) while in Eiffel one has to write:

 an_int := an_int + 1

An operator like ++ would be a procedure, and therefore 
change the state of the target. If Eiffel's INTEGER had 
this operator, it would become a mutable value, and 
lose the benefits it gets from being, along with the other 
numeric types, immutable. Mutable numeric types would 
allow clients to circumvent safety features of the type 
system (function parameter and attribute assignment 
restrictions for instance).

 --------------------

LAGE: What are Eiffel agents?

In the early years of Eiffel, the language had no routine types
because having routines as distinct entities outside objects
was seen as incompatible with the OO method.

Nevertheless, it has now been accepted that routines as first
class objects are essential and the 'agents' facility has been
introduced. It has been implemented at least in part in Eiffel
Software's compiler and SmartEiffel. Other active vendors are
likely to follow.

While this feature is new and the standard is being finalised,
the core concepts, routine types and tuples for representing
the parameters and return type of a routine, are now well
understood.

An ordinary agent is created within the current object,
which provides a context and makes the facility as expressive as
higher order functions (closures) in functional programming
languages.

 --------------------

LATR: Why are there no class attributes in Eiffel?

In Eiffel, the "once" function provides greater functionality in a
more disciplined way. The body of a "once" function is executed once
only per system (not per instance of the class), when it is first
called. Thereafter, the "once" function returns the same Result
without re-executing its body.

The "once" function can therefore be used to implement a shared
attribute of reference type (initialized on its first use).

A "once" function can be included in a mixin class. The shared
attribute returned by that once function is then available to all
instances of classes which inherit from the mixin class.

 --------------------

LPAR: How can I call the parent-class version of a redefined routine?

This was a problem that required the use of multiple inheritance or
synonyms with earlier versions of Eiffel, before the Precursor
construct was introduced.

This construct has now been implemented by all supported compilers,
so calling a parent version of a redefined routine just requires
using the Precursor keyword in the body of the redefinition.

The construct is described in an a paper at
http://www.eiffel.com/doc/manuals/language/precursor/

 --------------------

LEVC: Where can I find a comparison between Eiffel and other languages?

Ian Joyner's "C++ critique" includes a comparison between C++, Eiffel
and other languages. It is at the following URL:
http://www.progsoc.uts.edu.au/~geldridg/cpp/cppcv3.html and has also
been published as a book, Objects Unencapsulated (see QBOK).

In Richard Wiener's book "Software Development Using Eiffel: There can
be life after C++" (Prentice-Hall, ISBN 0-13-100686-X).

You can also find a comparison of Eiffel, C++, Java, and Smalltalk at
http://www.eiffel.com/doc/manuals/technology/oo_comparison/

 --------------------

LDES: Are there any destructors in Eiffel?

Eiffel objects are garbage-collected, so that there is no need for the
software developer to worry about whether, how and when to "destroy"
or "free" them in the software text.

Some implementations offer a "free" procedure for programmers who
absolutely want to remove an object manually. Such a procedure is "use
at your own risk" and is not needed in normal Eiffel development.

Coming back to normal usage, the need may arise to ensure that certain
operations will automatically take place whenever the garbage
collector reclaims an object. For example if an Eiffel object
describing a file becomes unreachable and hence is eventually
garbage-collected, you may want to ensure that the physical file will
be closed at that time. Most implementations of Eiffel provide a
mechanism for that purpose: procedure 'dispose' from the Kernel
Library class MEMORY.

Whenever the garbage collector collects an object, it calls 'dispose'
on that object. The procedure does nothing by default (so that a smart
GC will of course avoid executing any actual call). But any class may
inherit from MEMORY and redefine 'dispose' to perform appropriate
actions, such as closing a file. Such actions are sometimes called
"finalization". This technique achieves it conveniently.

Because there is no guarantee as to the order in which the garbage
collector will reclaim objects that have become unreachable, safe
redefinitions of 'dispose' should only act on external resources such
as file descriptors, database elements, window system resources etc,
not on Eiffel object structures themselves.

 --------------------

LDIS: How do I implement multiple inheritance efficiently?

People with a background in C++ or single-inheritance languages
often think that multiple inheritance carries a penalty because
it cannot be implemented using the classic dispatch table scheme
(where every polymorphic feature has a fixed position in a pointer
table that descendants can customise without breaking any code
using the fixed position of an ancestor's polymorphic feature.)

There are other ways to implement inheritance which allows Eiffel
not to suffer performance problems because of multiple inheritance.
Eiffel compilers generally use one of two methods.

In both cases, we need to assume that every type in the system is
assigned an integer identifier -- the type ID.

The first implementation is the 'sparse matrix' model. Every
polymorphic feature has an associated pointer table with an entry
for each type, indexed by type ID. This allows all polymorphic
calls to be executed at the (same) cost of a single pointer
dereferencing.

The immediate drawback of this, is that it generates a rather big
data table (a matrix of all polymorphic routines per all types in
a system). Fortunately, there are sparse matrix algorithms allowing
to compress these tables efficiently by carefully selecting the IDs
(an evident optimisation is to try to group all descendant next
to their parent so that only a short section of the type ID space
need be covered).

The other method is completely different and uses the equivalent of
an inspect statement on the type ID, calling the appropriate static
function for each concrete type. In this case, it is obvious that the
compiler needs a global knowlegde of the system: for each polymorphic
routine call, it needs to know all concrete subtypes really used in
the system, and all redefinitions of the routines.

At first sight, it could be thought that the inspect statement could
slow down the system. Actual compilers using this solution have proved
that they can be as efficient as (or more than) those implemented
using the first method.

It should now be clear that, for both methods, it is necessary
for the compiler to have at compile time -- or at the very least at
optimisation time -- a view of the complete system. This could appear
like a serious restriction but it is not much of a concern because
Eiffel compilers must have this view in the first place in order to
be able to differenciate between static and polymorphic routines --
all routines being potentially polymorphic in Eiffel -- and this
must be done to have compiled systems perform reasonably.

 --------------------

LISA: How does the `Iterating several actions' example in ETL work?

The example code page 176 of Eiffel: The Language, 2nd printing does
not work with any widely available compiler. It has confused and
puzzled many newcomers to the language and what it is supposed to
do is not clearly defined in the book.

What the example should do is as follows. When a feature is replicated
under multiple inheritance (renamed so that the feature is now known
under two names) and in the same inheritance clause a routine or
attribute its source text references is also replicated, the routine
body of the first feature should be adapted to call the corresponding
replicated features on each path of the inheritance. The mechanism
is not intended to scale to more complex cases where the replication
does not occur in a single inheritance clause.

This feature is now considered obsolete because agents (see LAGE) are
now available and provide a more convenient solution for this pattern.

 --------------------

LORB: Is COM/CORBA supported?

COM or CORBA support is not built into the language. Most
commercial vendors supporting Windows have a COM package
that is tied to their compiler.

There is an open source CORBA object request broker,
MICO/E, at http://www.math.uni-goettingen.de/micoe/

2ab, Inc., at http://www.2ab.com/, has an Eiffel
binding for their CORBA package which works with ISE
Eiffel.



Section 2 of 2 - Prev - Next

Back to category Programming - Use Smart Search
Home - Smart Search - About the project - Feedback

© allanswers.org | Terms of use

LiveInternet