Channels ▼
RSS

Tools

The Eiffel Programming Language

Source Code Accompanies This Article. Download It Now.


OCT93: The Eiffel Programming Language

Robert "Rock" Howard is president of Tower Technology Corporation, creator of the TowerEiffel system, and chairman of NICE, the Eiffel Consortium. You can contact him at 2701 Stratford Drive, Austin, TX 78746 or at rock@twr.com.


With software complexity mushrooming because of ever-increasing user expectations, programmers should avoid "accidental" complexities imposed by languages and tools whenever possible. The Eiffel programming language, perhaps more than others, removes unnecessary complexities without limiting the ability to express the inherent complexity of software.

Eiffel combines object-oriented (OO) capabilities with a unique focus on software "correctness" and reusability. Eiffel programs can express abstractions in a clear manner using a syntax that's simple to learn and use. Eiffel can be used for OO design-specification, or as a full-fledged, software-engineering tool.

Eiffel is a class-based language that reinforces OO design. It includes multiple and repeated inheritance, selective exporting, strong type checking, parameterized classes, dynamic binding, garbage collection, feature renaming, exception handling, and persistency. Eiffel is implemented via sophisticated compilers that perform dependency analysis and optimization. To speed the development cycle, some implementations include interpretation.

Since its introduction in 1986, Eiffel has attracted an international following in academia and engineering and is used extensively by in-house and commercial software-development projects. Eiffel is the only OO language outside of Smalltalk and C++ with multiple commercial implementations. The Eiffel trademark is owned by the Nonprofit International Consortium for Eiffel (NICE), an independent consortium that administers and enforces language and core library standards.

Eiffel's major benefit is the reduction of software-maintenance costs. This comes about in two ways. First, Eiffel supports the development of correct software via the use of semiformal assertion technology. Second, Eiffel supports the development and use of reusable software. These concepts are intimately linked since software correctness is a prerequisite for effective software reuse.

Eiffel is a "pure" object-oriented language. Every value is either an object, a reference to an object, or void. (Void denotes the state of a reference that is not currently attached to an object.) While Eiffel syntax resemble Pascal, it was designed as a class-based, statically-typed object-oriented language.

All Eiffel classes implicitly inherit from class ANY, and include both shallow and deep copying, cloning, comparison, and so on. Eiffel supports a clean mechanism allowing you to easily add capabilities to this class.

Eiffel has no formal notion of pointers. Conceptually, all feature arguments are object references. The compiler can "decide" to implement a feature call in an appropriate manner--given the actual type of the objects used as arguments. Compared to languages where there are choices between pointer and reference semantics, Eiffel's uniformity in calling conventions simplifies class interfaces.

Eiffel has standard mechanisms for interfacing with C, including calling C from Eiffel, and Eiffel from C. Some implementations include extensions that allow the use of C macros or inline C within designated Eiffel routines. Come compiler vendors also include mechanisms for accessing Eiffel object data or features from C.

Eiffel implementations come with a variety of kernel classes handing basic types--integers, reals, floats, Booleans, strings, arrays, bits, and so on. The compiler knows about these types and can produce optimized code when they're used. Other kernel classes are basic I/O, standard files, and exceptions. NICE is working to standardize the kernel classes.

It is easy to add persistency to Eiffel. Interactive Software Engineering (Goleta, CA) provides the STORABLE class that allows for persistence to classes that inherit from it. Tower Technology (Austin, TX) provides a library that can encode any Eiffel object into a heterogeneous, packed binary format that can be decoded into a separate process on another machine, or at a future time. SIG Computer (Braunfels, Germany) has specialized FILE classes used for simple persistency.

Improving Software Correctness

Eiffel eliminates the possibility of entire categories of errors--memory management errors, unintended side effects caused by poor encapsulation, bad links from erroneous makefiles, improper routine dispatching due to type errors or confusion, and the like. Eiffel also "attacks" errors in application logic and semantics via its advanced assertion technology. Assertions support the contract metaphor, aid feature and class comprehensibility, interact with the exception-handling capabilities, and improve confidence in system reliability. Debugging time, especially defect location, is usually cut drastically when assertions are used in Eiffel classes.

Assertions in Eiffel come in two flavors--preconditions and postconditions. (Class invariants, loop invariants, loop variants, and checks are also supported, but for the sake of this argument you can lump them in with postconditions.) Preconditions require that arguments provided to feature calls are acceptable, and that the object is in a state such that the feature call can be handled. Postconditions ensure that a chunk of code performed correctly, provided an acceptable result, and left the object in a correct state.

The preconditions, postconditions, and class invariants are treated as inherent parts of the class interface and are retained under inheritance to enforce required behavior even for subclasses. Eiffel's automatic interface extraction tool treats assertions as equally important as routine signatures. Unlike other languages, semantic assumptions expressed via assertions are explicit and public.

When a run-time error occurs, the Eiffel exception-handling capability kicks in. If no handler is designated for a given assertion failure, it's handled by the Eiffel run-time system, which dumps trace and other information. Precondition failures denote errors in the feature caller, while postconditions denote errors in the feature body. Thus the liberal use of assertions usually immediately isolates many run-time defects. (For further discussion of software correctness, see "Writing Correct Software with Eiffel" by Bertrand Meyer, DDJ, December 1989.)

Eiffel supports the reuse of classes via inheritance, parameterization, and composition. The inheritance mechanism allows renaming and redefinition of features so that derived classes can be easily specialized. Parameterization goes beyond simple template construction as type parameters can be constrained. This allows the constraint class's capabilities to be used within the enclosing parameterized class, enabling the implementation of high-level abstract data types. Eiffel also includes an indexing clause for locating reusable classes.

Exploring Eiffel

To examine Eiffel's syntax, let's begin by looking at the linked list example (see the Eiffel code on page 122, which is part of the article, "Comparing Object-oriented Languages"). At first glance, the syntax seems similar to Pascal--assignment via :=, If statements, routine declarations, and the optional semicolon statement separators are familiar elements borrowed from Pascal. The loop construct is demonstrated in class MY_LIST. The from/until/loop/end pattern can also be supplemented by optional loop invariant and loop variant assertions.

Distinguishing Eiffel from Pascal are the clauses introduced by the inherit, creation, and feature keywords. The optional inherit clause lists the class or classes that are inherited. In this clause, it's possible to rename individual features and otherwise join classes so that naming conflicts don't occur. The optional creation clause lists procedures that are available to initialize a newly-created object. The feature clause specifies the export status for one or more Eiffel attributes and/or routines grouped within it. (Eiffel uses the term "feature" to denote attributes or routine since, in some cases, they may be substituted for each other.)

Although not specified by the language definition, current Eiffel implementations expect that each class definition resides in a separate file with a suffix of .e. The name of the class need not match the file name, although that's the convention. Related classes are often grouped together in directories called "clusters." An important cluster is the "kernel" cluster that's provided with each Eiffel implementation. The kernel includes the low-level base classes, including the basic types--integer, real, double, character, array, string, file, and others.

A configuration file names the clusters where classes may be found for building an executable. It also names a class as the top-level class, and a feature as the initial feature to call within the top-level class. Various debugging options and other system-building parameters are specified. Interactive Software Engineering, and Tower Technology use the "LACE" configuration file format while SIG Computer uses the "pdl" format. (NICE will likely standardize on a single format in the future.)

To build the sample Eiffel system, the configuration file specifies DRIVER as the top-level class and make as the initial feature to call. The code for make in class DRIVER performs the same work as the C++ main() function.

The !! syntax denotes object creation. Some classes denote one or more procedures as "creation procedures" by naming them in the creation clause near the beginning of the class. If a class has no creation procedure, then the object is created without a call (for example, !!list1). If a class has at least one creation procedure, then creation requires a call such as (!!n1.set( 10 ).

The set_data routine in class ELEMENT can be used for either creating an instance of ELEMENT, or as a normal feature call. If required, export controls can be used to make a routine available only for creation.

The Eiffel class PRINTABLE replaces MyListData in the C++ code example. Note that PRINTABLE is marked deferred so that no objects of type PRINTABLE can be created. Instead, it's used as an abstract data type and inherited from it to get reliable polymorphic behavior. Also, the class LIST is a general-purpose version of MY_LIST. LIST takes a generic parameter named T. To use LIST, you declare it with an actual generic parameter; for instance, this_list : LIST(HAT);. Thus this_list holds objects of type HAT and/or objects that inherit from class HAT.

Class MY-LIST also has a generic parameter, but there's a restraint on its parameter [T->PRINTABLE]. This means that only objects that inherit from PRINTABLE are allowed to be used wherever T is used within the class. The result is that the compiler ensures that the call to print_self (which was defined as a deferred feature in class PRINTABLE) will always work.

More Code Notes

Eiffel assertions can have optional labels that are echoed to the standard error output if the exception caused by an assertion failure is not handled; see the invariant clause of class LIST.

Every attribute and routine is defined within the scope of a feature clause that denotes its export status. Feature [NONE] is equivalent to private. Feature [ANY] is equivalent to public since all Eiffel classes automatically inherit from class ANY. Feature [LIST} in class ELEMENT is an example of selective exporting. Only class LIST and classes that inherit from it may invoke these features.

Eiffel automatically initializes all fields to default values, so some of the initialization logic from the C++ code is not necessary.

The I/O calls (put_string, put_int, put_newline, and put_char) are from the SIG's Eiffel/S BASIC_IO class. Interactive Software's Eiffel 3 has a slightly different I/O library. Tower uses either ISE or Eiffel/S style. NICE is developing a single I/O standard and other core capabilities.

Extending the Linked List Example

Although the Eiffel linked list implementation is wordier than the C++ implementation, it's safer, more understandable, and does much more. For example, it includes a general-purpose list, not one specialized for printable objects. Furthermore, the specialized class MY-LIST is safer since nonconforming objects can't be added to it.

The routines in Example 1 are additions to class LIST for removing items. Eiffel will reclaim an item's memory as is appropriate. Doing this in C++ is painful because you must build or include a garbage collector or a sophisticated reference counting system to know when to call destructors safely.

The routine in Example 2 can be added to class LIST to return individual items. In Eiffel, there are two approaches to using this capability. One approach shown in Example 3 is to fetch the item as conforming to a type that includes the needed capabilities and then rely on dynamic typing. The other approach is shown in Example 4 and uses an assignment attempt. This will assign an object only if it conforms to the target reference. Otherwise the reference is set to VOID.

These examples show the Eiffel objects retain type information. Because C++ objects don't retain type information, the examples are hard to duplicate. Instead, C++ has the dangerous type coercion capability that can't match the safety and flexibility of these examples.

Development Automation

According to the Eiffel philosophy, compilers should take on many of the automatable tasks involved in building applications. For example, replying on programmers for dependency-analysis information (makefiles) is a waste of time and a source of problems. Requiring the maintenance of separate class interface files, including the complex include file invocation order, is another nonproductive effort.

Eiffel compilers take on much of the burden of performing system-wide and local optimizations. For example, all Eiffel features are "virtual" as far as the Eiffel programmer is concerned. The Eiffel compiler locates all appropriate opportunities for in-lining or removing unnecessary code and eliminating unnecessary dynamic binding. Programmers focus optimization efforts on choosing proper data structures and streamlining object interactions. The compiler also eliminates dead code, permitting you to focus on data structures and object interactions.

Eiffel compilers take on the task of fully checking for correct type usage, without imposing a confusing or limited type system on you. In Eiffel, subclassing and subtyping are one and the same, making the use of inheritance particularly powerful and easy. Eiffel supports the more natural, covariant type system that allows classes to be easily specialized in concert.

Final Thoughts

Eiffel encourages you to use languages and tools appropriately; that is, to use an OO language for implementing OO designs. You can then use a portable assembler such as C for low-level optimizations and operating-system interfaces, or an AI tool or language where a logic-based approach is needed. As long as the languages and tools can interface appropriately, this approach yields better results than the use of hybrid languages or tools.

Eiffel's support for semiformal, parameterizable specifications allow the construction of robust, reusable software components. The clear syntax couples with the elucidation of assumptions via assertions makes it easier to share these components with confidence. The result is that the real benefits of object-oriented programming are more fully realized.

Example 1:

   is_empty : BOOLEAN is
         -- return TRUE if LIST is empty
      do
         Result := ( head = void )
      end ; -- is_empty
   remove is
         -- remove the first list item
      require
         not_empty: not is_empty
      do
         head := head.next ;
         if head = void
          then
            tail := void ;
         end;
      end ; -- remove

Example 2:

   item : T is
         -- return the head of the list
      require
         not is_empty
      do
         Result := head
      end ; -- item

Example 3:

      local
         this_list : MY_LIST[PRINTABLE] ;
         pr : PRINTABLE ;
      do
         -- create the list
         !!this_list ;
         -- add items to the list (not shown)
         ...
         -- fetch an item from the list
         if not this_list.is_empty
          then
            pr := this_list.item ;
            pr.print_self ; -- dynamic binding still works!
         end

Example 4:

      local
         this_list :
      MY_LIST[PRINTABLE] ;
         pt : MY_POINT
      do
         -- create the list
         !!this_list ;
         -- add items to the list
         ... (not shown)
         -- fetch an item from the list
         pt ?= this_list.item 
         if pt /= void
          then -- it was a MY_POINT
            pt.print_self ;
         end ;



EIFFEL SOURCE CODE EXAMPLE

class DRIVER
   -- In Eiffel, the top level driver is an object, too.
inherit
   BASIC_IO
creation
   make
feature {ANY}
   make is
      -- run this test driver
      local
      list1, list2 : MY_LIST[PRINTABLE] ;
      n1, n2 : MY_NUMBER ; -- a kind of PRINTABLE
      p1, p2 : MY_POINT ;  -- also a kinf of PRINTABLE
      do
         -- create the various objects
      !!list1 ;
      !!list2 ;
      !!n1.set( 10 ) ;
      !!n2.set( 20 ) ;
      !!p1.set( 2, 3 ) ;
      !!p2.set( 4, 5 ) ;

      list1.add_to_list( n1 ) ;
      list1.add_to_list( n2 ) ;
      list1.add_to_list( p1 ) ;
      list2.add_to_list( n2 ) ; -- objects can be in more than one list
      list2.add_to_list( p1 ) ;
      list2.add_to_list( p2 ) ;
      list2.add_to_list( list1 ) ; -- list 1 is an element of list 2

      put_string( "list1:%N" ) ;
      list1.print_self ;
      put_string( "list2:%N" ) ;
      list2.print_self ;
      end -- make
end -- DRIVER
deferred class PRINTABLE
   -- insures that 'print_self' is implemented
inherit
   BASIC_IO
feature {ANY}
   print_self is
      -- print yourself
      deferred
      end -- print_self
end -- PRINTABLE
class MY_NUMBER
   -- holds and can print an integer
inherit
   PRINTABLE
creation
   set
feature {ANY}
   value : INTEGER ;
   set( new_value : INTEGER ) is
      -- set this number
      do
      value := new_value ;
      end ; -- make
   print_self is
      -- print the value
      do
      put_string( "Number: " ) ;
      put_int( value ) ;
      put_newline ;
      end ; -- print_self
end -- MY_NUMBER
class MY_POINT
   -- holds and can print an x,y pair
inherit
   PRINTABLE
creation
   set
feature {ANY}
   x, y : INTEGER ;
   set( new_x : INTEGER; new_y : INTEGER ) is
      -- set this point
      do
      x := new_x ;
      y := new_y
      end ; -- set
   print_self is
      -- print the value
      do
      put_string( "Point: " ) ;
      put_int( x ) ;
      put_char( ',' ) ;
      put_int( y ) ;
      put_newline ;
      end ; -- print_self
end -- MY_POINT
class ELEMENT[T]
   -- holds an object reference and a
   -- single link to another ELEMENT[T]
creation
   set_data
feature {LIST}
   data : T ;
   next : ELEMENT[T] ;
   set_data( new_data : T ) is
      -- set data to the new_data
      do
      data := new_data
      end ; -- set_data
   set_next( new_next : ELEMENT[T] ) is
      -- set next to the element
      do
      next := new_next
      end ; -- set_next
end -- class ELEMENT
class LIST[T]
   -- a generic linked list class
feature {ANY}
   add_to_list( data : T ) is
      -- add to the end of the list
      local
      new_element : ELEMENT[T] ;
      do
      !!new_element.set_data( data ) ;
      if head = void
       then
         head := new_element ;
      else
         tail.set_next( new_element ) ;
      end
      tail := new_element ;
      end ; -- add_to_list
feature {NONE}
   head, tail : ELEMENT[T] ;
invariant
   tail_next_is_void: tail.next = void ;
   tail_void_when_head_void: head = void implies tail = void
end -- LIST
class MY_LIST[T->PRINTABLE]
   -- A printable list which holds printable data
inherit
   LIST[T]
   PRINTABLE
feature {ANY}
   print_self is
      -- print the list elements
      local
      el : ELEMENT[T] ;
      do
      from
         el := head
      until
         el = void
      loop
         el.data.print_self
         el := el.next ;
      end ;
      end ; -- print_self
end -- MY_LIST


Copyright © 1993, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video