Difference between revisions of "EiffelBase2"

(Design overview)
(Iterators)
 
(46 intermediate revisions by 3 users not shown)
Line 3: Line 3:
 
==Overview==
 
==Overview==
  
EiffelBase2, currently under development, is a general-purpose data structures library for Eiffel. It is intended as the future replacement for the [[EiffelBase]] library ("Classic EiffelBase" in this document), which has for many years played a central role in Eiffel development.  
+
EiffelBase2 is a general-purpose data structures library for Eiffel. It is intended as the future replacement for the [[EiffelBase]] library, which has for many years played a central role in Eiffel development.
  
==Design goals==
+
==Download==
 +
 
 +
The latest version of the EiffelBase2 source code is available from the repository: [https://bitbucket.org/nadiapolikarpova/eiffelbase2/ https://bitbucket.org/nadiapolikarpova/eiffelbase2/]
 +
 
 +
The source code is divided into two clusters:
 +
 
 +
* <e>structures</e> - Data Structures, the core of EiffelBase2. The rest of current document is about this cluster.
 +
 
 +
* <e>mml</e> - Mathematical Model Library: a library of immutable classes used in ''model-based contracts'' (see below).
 +
 
 +
==Goals==
  
 
The design goals for EiffelBase2 are:
 
The design goals for EiffelBase2 are:
Line 11: Line 21:
 
*Verifiability. The library is designed to allow proofs of correctness.
 
*Verifiability. The library is designed to allow proofs of correctness.
  
*Full contracts. Partly as a result of the verifiability goal, but also for clarity and documentation, the contracts associated with classes and features should describe the relevant semantics in its entirety.
+
*Complete specifications. Partly as a result of the verifiability goal, but also for clarity and documentation, the contracts associated with classes and features should describe the relevant semantics in its entirety.
  
 
*Simple and consistent hierarchy, in particular avoidance of descendant hiding and of "taxomania" (classes not representing a meaningful abstraction, unnecessary inheritance links).
 
*Simple and consistent hierarchy, in particular avoidance of descendant hiding and of "taxomania" (classes not representing a meaningful abstraction, unnecessary inheritance links).
  
*As in Classic EiffelBase, application of a systematic classification (a theory) of fundamental data structures.
+
==Design==
 +
On the top level EiffelBase2 differentiates between ''containers'' and ''streams''. A container is a finite storage of values, while a stream provides linear access to a set of values. A stream is not necessarily bound to a container, e.g. <e>RANDOM</e> stream observes an infinite sequence of pseudo-random numbers. Streams that traverse containers are called ''iterators''.
  
*Full traversal mechanisms, based on external cursors (with internal cursors also provided when useful).
+
Below you can find the class diagram of EiffelBase2, split into two hierarchies: containers and streams/iterators.
 +
All EiffelBase2 class names start with <e>V_</e> (for ''Verified''), but the prefix is omitted in the current document for brevity.
  
*Client-interface compatibility with corresponding classes in Classic EiffelBase, whenever it does not conflict with the preceding goals.
+
In the diagrams below asterisk and italics font indicates a <e>deferred</e> class.
 +
Lighter fill color indicates that the class provides an ''immutable'' interface to the data,
 +
in other words, it is impossible to modify the content of the container using this interface.
 +
 +
[[Image:eb2_container.png|1000px|thumb|none|Container class hierarchy]]
  
==Design overview==
+
[[Image:eb2_iterator.png|1000px|thumb|none|Iterator class hierarchy]]
===Traversal mechanisms and classification criteria===
+
A design decision that significantly influenced the architecture of the library is using external iterators to traverse containers as apposed to internal ones available in classic EiffelBase.
+
In the design inspired by external iterators we found it natural to separate ''containers'' (with their measurements and modification means) from interfaces to access elements of containers, here called ''observers''.
+
  
On the observer side we distinguish ''maps'' (with the defining property "accessing elements by unique keys") and iterators (providing linear traversal). An observer can be either ''used'' by a container (as a supplier) or ''inherited'' by it. The first case produces an external access mechanism and is useful when multiple instances of an observer for the same container can exist at the same time; the second case results in an internal access mechanism and allows only one instance of an observer per container at a time. In the rest of the library maps are mostly inherited and iterators are mostly used, but this is not enforced by the design. For infinite sequences like <e>RANDOM</e> it makes sense to ''inherit'' from an iterator, because they cannot have more than one active iterator.
+
==Usage examples==
  
On the other side, a ''container'' is a finite storage of values. Containers are deliberately confined to finite structures and understood real, physical storage. Infinite structures, instead, are usually represented as functions or mechanisms that are external to a program. Most of the time we can only access their elements (not add or remove) and for this purpose we have observers: maps and iterators (the latter in the infinite case are called ''streams'').
+
===Immutable interfaces===
 +
With immutable interfaces you can give your clients read-only access to a container you store:
 +
<e>
 +
class TRAM_LINE
  
Containers may be classified based on different means of modification, but previous experience shows that a complete and clean classification here is impossible (there are two many kinds of modification, basically one per "concrete" data structure). So concrete data structures mostly just inherit directly from <e>CONTAINER</e>. There is one exception: <e>SEQUENCE</e>, which represents a sequence of values accessible by indices from a continuous interval. It serves as a common ancestor for ARRAY and LIST and factors out their common search and replacement mechanisms.
+
feature -- Access
 +
   
 +
    stations: V_SEQUENCE [STATION]
 +
            -- Stations on the line.
 +
            -- (Clients can traverse the stations, but cannot replace them, remove or add new ones.)
 +
        do
 +
            Result := station_list
 +
        end
  
Below you can find the class diagram of EiffelBase2, split into two hierarchies (according to connectedness). The first one is a hierarchy of containers and maps. Note: dash-bordered ovals stand for classes that might be included in the library, but are not the first priority of the developers. 
+
feature {NONE} -- Implementation
[[Image:eb2_container.png|800px|thumb|none|Container class hierarchy]]
+
  
The second one is a hierarchy of streams and iterators.
+
    station_list: V_LIST [STATION]
[[Image:eb2_iterator.png|800px|thumb|none|Iterator class hierarchy]]
+
            -- List of line stations.
 +
end
 +
</e>
  
===Comparison criteria===
+
===Iterators===
Another important design decision for a data structures library is how to represent and modify comparison criteria for container elements.
+
Here is how you can iterate through any container:
Adjustable comparison criterion is used is search operations and to define the uniqueness property in sets and tables (uniqueness of keys).
+
<e>
# Classic EiffelBase uses the boolean <e>object_comparison</e> attribute in <e>CONTAINER</e> for this purpose.
+
do_something (container: V_CONTAINER [INTEGER])
# Gobo.structures uses another approach: storing equivalence as an object of a specific class, which can be redefined by the user to implement an arbitrary equalivalence relation.
+
    do
# A similar approach would be to use agents to store the equalivalence relation (no need to create classes). The downside is that agents do not serialize well.
+
        across
# Another approach is to make <e>has (x: G)</e> and <e>search (x: G)</e> always use <e>=</e>, but also introduce <e>exists (p: PREDICATE [ANY, TUPLE [G]])</e> and <e>satisfy (p: PREDICATE [ANY, TUPLE [G]])</e>. This is more flexible (better fits cases when the predicate is not an equivalence), but duplicates the number of search features and doesn't solve the problem with sets.
+
            container as i
 +
        loop
 +
            print (i.item)          
 +
        end
 +
    end
 +
</e>
  
Additional question (if the comparison criterion is stored in the container) is whether it can be changed during the object lifetime. A well known practice is that in sets it is not allowed to change if the set is not empty. This gives rise to <e>changeable_comparison_criterion</e> query in CONTAINER.
+
The same thing using the explicit syntax:
Note that for most set and table implementations (hashed, sorted) the comparison critearion is defined by the corresponding hash function, order, etc. and cannot be modified arbitrary even when the set or table is empty.
+
<e>
 +
do_something (container: V_CONTAINER [INTEGER])
 +
    local
 +
        i: V_ITERATOR [INTEGER]
 +
    do
 +
        from
 +
            i := container.new_cursor
 +
        until
 +
            i.after
 +
        loop
 +
            print (i.item)
 +
            i.forth
 +
        end
 +
    end
 +
</e>
  
The strategy chosen in EiffelBase2 is to treat differently comparison criteria on which a set or a table is based and ones just used for search. For the first the Gobo approach is taken (2 in the list above), because it is more flexible than in EiffelBase, as we can use whatever equivalence relation we want instead of just <e>is_equal</e> (useful for library classes). Moreover, the equivalence relation is passed as an argument to the creation procedure and cannot be changed afterward.
+
Here is some more advanced stuff you can do with lists:
 +
<e>
 +
do_something (list: V_LIST [INTEGER])
 +
    local
 +
        i: V_LIST_ITERATOR [INTEGER]
 +
    do
 +
        -- Find the last 0 at or before position 5:
 +
        list.at (5).search_back (0)
 +
        -- Find the first positive element at or after position 5:
 +
        i := list.at (5)
 +
        i.satisfy_forth (agent (x: INTEGER): BOOLEAN do Result := x > 0 end)
 +
        -- And insert a 0 after it:
 +
        i.extend_right (0)
 +
    end
 +
</e>
  
For search operations the approach 4 is taken, because it doesn't clutter the container state with properties that don't pertain to the container itself, but to specific search operations.
+
===Sets and tables===
 +
 
 +
Here is how you create and use a simple hash table (keys must inherit from HASHABLE):
 +
<e>
 +
do_something
 +
    local
 +
        table: V_HASH_TABLE [STRING, INTEGER]
 +
    do
 +
        create table.with_object_equality
 +
        table ["cat"] := 1
 +
        table ["dog"] := 2
 +
        print (table ["cat"] + table ["dog"])
 +
        -- Prints "3"
 +
    end
 +
</e>
 +
 
 +
If you need a custom hash function or equivalence relation on keys, you can use V_GENERAL_HASH_TABLE, for example:
 +
<e>
 +
do_something
 +
    local
 +
        table: V_GENERAL_HASH_TABLE [STRING, INTEGER]
 +
    do
 +
        -- Create case-insensitive table:
 +
        create table.make (
 +
            agent {STRING}.is_case_insensitive_equal,
 +
            agent (s: STRING): INTEGER do Result := s.as_lower.hash_code end
 +
        )
 +
        table ["cat"] := 1
 +
        table ["dog"] := 2
 +
        print (table ["CAT"] + table ["dog"])
 +
        -- Prints "3"
 +
    end
 +
</e>
 +
 
 +
Similar style applies to <e>V_HASH_SET</e> and <e>V_GENERAL_HASH_SET</e>, <e>V_SORTED_TABLE</e> and <e>V_GENERAL_SORTED_TABLE</e>, <e>V_SORTED_SET</e> and <e>V_GENERAL_SORTED_SET</e>.
 +
 
 +
Sometimes you want to use an object as a key in a table,
 +
but the content of the object can change, so you cannot derive a hash code (or order) from any of its attributes.
 +
EiffelBase2 provides a utility class <e>V_REFERENCE_HASHABLE</e>,
 +
which derives a hash code from the object's identity rather than its content.
 +
For example:
 +
<e>
 +
class
 +
    CAR
 +
inherit
 +
    V_REFERENCE_HASHABLE
 +
 
 +
feature -- Access
 +
    color: COLOR
 +
 
 +
    location: POINT
 +
 
 +
feature -- Modification
 +
...
 +
end
 +
 
 +
 
 +
class GUI_APPLICATION
 +
 
 +
feature
 +
    cars: V_HASH_TABLE [CAR, CAR_VIEW]
 +
            -- Cars in the city and their graphical views.
 +
 
 +
    escape_police (c: CAR)
 +
            -- Drive `c' away, repaint it and update its view.
 +
        require
 +
            car_in_city: cars.has_key (c)
 +
        do
 +
            c.move (100, 100) -- change location
 +
            c.repaint (white) -- change color
 +
            cars [c].update -- ... but still can be used to access its view
 +
        end
 +
end
 +
</e>
 +
 
 +
===Stream piping===
 +
 
 +
Iterators in EiffelBase2 are a special case of streams. Sometimes you can avoid writing a loop by piping an input stream into an output stream, for example:
 +
<e>
 +
do_something
 +
    local
 +
        array: V_ARRAY [INTEGER]
 +
    do
 +
        create array.make (1, 10)
 +
        -- Fill array with random integers:
 +
        array.new_cursor.pipe (create {V_RANDOM})
 +
        -- Fill array with values parsed from a string:
 +
        array.new_cursor.pipe (create {V_STRING_INPUT [INTEGER]}.make ("1 2 3 4 5 6 7 8 9 10", agent {STRING}.to_integer))
 +
        -- Print array elements into standard output:
 +
        (create {V_STANDARD_OUTPUT}).pipe (array.new_cursor)
 +
        -- Prints "1 2 3 4 5 6 7 8 9 10 "
 +
    end
 +
</e>
  
 
==Model-based contracts==
 
==Model-based contracts==
 +
===Models===
 +
EiffelBase2 is specified using the ''model-based contracts'' specification method. This method prescribes that each class defines a ''model'' - a mathematical object that represents explicitly its abstract state space. The model of a class is expressed as tuple of one or more predefined mathematical objects: booleans, integers, sets, relations, maps, sequences or bags. We also introduce a special mathematical sort for object IDs (references).
 +
 +
Such mathematical objects in the program are represented by ''model classes'', which are immutable and thus are straightforward translations of mathematical definitions. The Mathematical Model Library (MML), which is a part of EiffelBase2 contains model classes for sets, relations, maps, sequences and bags. Boolean and integer components of models are represented by standard classes <e>BOOLEAN</e> and <e>INTEGER</e>. Finally, arbitrary reference classes can be used as model components to denote the mathematical sort of references.
 +
 +
For example, a mathematical sequence of elements is a model for a stack or a queue.
 +
A triple consisting of a reference to the target container, the sequence of elements and an integer is a model for an iterator.
 +
 +
The value of each component of the model is defined by a ''model query''. You define the model of the class in the source code by listing its model queries under the tag <e>model</e> in the class's <e>note</e> clause. For example:
 +
<e>
 +
note
 +
    model: target, sequence, index
 +
 +
class V_LIST_ITERATOR [G]
 +
 +
feature -- Access
 +
 +
    target: V_LIST
 +
            -- List to iterate over.
 +
        deferred
 +
        end
 +
 +
    index: INTEGER
 +
            -- Current position.
 +
        deferred
 +
        end
 +
...
 +
feature -- Specification
 +
    sequence: MML_SEQUENCE [G]
 +
            -- Sequence of elements in `target'.
 +
        note
 +
            status: specification
 +
        deferred
 +
        end
 +
end
 +
</e>
 +
 +
Here we declared the model of class <e>V_LIST_ITERATOR</e> consisting of tree components:
 +
a reference (to <e>V_LIST</e>), a mathematical sequence of type <e>MML_SEQUENCE</e> and an integer index.
 +
As you can see, model queries are not necessarily introduced specifically for specification purposes (as is the case with <e>sequence</e>);
 +
regular queries meant to be called from the code can be also reused as model queries (as <e>target</e> and <e>index</e> in this example).
 +
We attach a <e>status: specification</e> note to a query to indicate that its primary purpose is specification.
 +
Those annotations can be used by tools to check, if a specification-only feature accidentally got called from non-specification code,
 +
and to remove such features from the code before compiling it.
 +
 +
===Contracts===
 +
The purpose of introducing model queries is to define the postconditions of regular features in their terms.
 +
For queries we define their result (or the model of the result, in case the query returns a fresh object) as a function of the model of <e>Current</e> and (the models of) the arguments.
 +
For commands we define their effects on the model queries of <e>Current</e> and the arguments.
 +
We also supply them with <e>modify</e> clauses that list all the model queries whose values are allowed to be changed by the command.
 +
These clauses can be used by tools to generate additional postconditions <e>m = old m</e>
 +
for each model query <e>m</e> of <e>Current</e> and other arguments that is not mentioned in the <e>modify</e> clause.
 +
 +
The model-based contracts approach does not constrain the way in which you write preconditions:
 +
it is not necessary to express them through model queries if they can be conveniently expressed otherwise.
 +
 +
Class invariants in the model-based contracts approach constrain the values of model queries to make them reflect precisely the abstract state space of the class.
 +
 +
Let us add a couple of features and model-based contracts into the class <e>V_LIST_ITERATOR</e> shown above:
 +
<e>
 +
note
 +
    model: target, sequence, index
 +
 +
class V_LIST_ITERATOR [G]
 +
 +
feature -- Access
 +
 +
    item: G
 +
            -- Item at current position.
 +
        require
 +
            not_off: not off
 +
        deferred
 +
        ensure
 +
            definition: Result = sequence [index]
 +
        end
 +
...
 +
 +
feature -- Status report
 +
 +
    off: BOOLEAN
 +
            -- Is current position off scope?
 +
        deferred
 +
        ensure
 +
            definition: Result = not sequence.domain [index]
 +
        end
 +
...
 +
 +
feature -- Extension
 +
 +
    extend_right (v: G)
 +
            -- Insert `v' to the right of current position.
 +
            -- Do not move cursor.
 +
        note
 +
            modify: sequence
 +
        require
 +
            not_off: not off
 +
deferred
 +
ensure
 +
    sequence_effect: sequence |=| old (sequence.front (index) & v + sequence.tail (index + 1))
 +
end
 +
...
 +
 +
invariant
 +
    index_in_range: 0 <= index and index <= sequence.count + 1
 +
end
 +
</e>
 +
 +
===Inheritance===
 +
If a class <e>B</e> inherits from <e>A</e> it is free to choose, whether to reuse each of <e>A</e>'s model queries to represent its own model, as well as introduce new model queries. If <e>B</e> does not reuse an <e>A</e>'s model query is has to provide a ''linking invariant'': a definition of the old model query in terms of <e>B</e>'s model. Linking invariants explain the parent's model in terms of the heir's model and thus make sure that the inherited model-based contracts make sense in the heir.
 +
 +
For example, look at <e>V_SET</e>, which inherits directly from <e>V_CONTAINER</e>:
 +
<e>
 +
note
 +
    model: bag
 +
class V_CONTAINER [G]
 +
...
 +
end
 +
 +
note
 +
    model: set
 +
class V_SET[G]
 +
...
 +
invariant
 +
    ...
 +
    bag_domain_definition: bag.domain |=| set
 +
    bag_definition: bag.is_constant (1)
 +
end
 +
</e>
 +
Here the linking invariant, provided as part of the class invariant in <e>V_SET</e> completely defines an old model query <e>bag</e> in terms of a new model query <e>set</e>.
  
 
==Status and roadmap==
 
==Status and roadmap==
  
Development of EiffelBase has started as a project at ETH Zurich; see the [http://eiffelbase2.origo.ethz.ch/ project page]. Documentation and other material will soon be transferred to the present page.
+
EiffelBase2 is currently being developed as a project at ETH Zurich.
 +
 
 +
It has been used in the following projects:
 +
* Traffic 4: modeling public transportation in a city [https://bitbucket.org/nadiapolikarpova/traffic https://bitbucket.org/nadiapolikarpova/traffic]

Latest revision as of 02:04, 30 April 2014


Overview

EiffelBase2 is a general-purpose data structures library for Eiffel. It is intended as the future replacement for the EiffelBase library, which has for many years played a central role in Eiffel development.

Download

The latest version of the EiffelBase2 source code is available from the repository: https://bitbucket.org/nadiapolikarpova/eiffelbase2/

The source code is divided into two clusters:

  • structures - Data Structures, the core of EiffelBase2. The rest of current document is about this cluster.
  • mml - Mathematical Model Library: a library of immutable classes used in model-based contracts (see below).

Goals

The design goals for EiffelBase2 are:

  • Verifiability. The library is designed to allow proofs of correctness.
  • Complete specifications. Partly as a result of the verifiability goal, but also for clarity and documentation, the contracts associated with classes and features should describe the relevant semantics in its entirety.
  • Simple and consistent hierarchy, in particular avoidance of descendant hiding and of "taxomania" (classes not representing a meaningful abstraction, unnecessary inheritance links).

Design

On the top level EiffelBase2 differentiates between containers and streams. A container is a finite storage of values, while a stream provides linear access to a set of values. A stream is not necessarily bound to a container, e.g. RANDOM stream observes an infinite sequence of pseudo-random numbers. Streams that traverse containers are called iterators.

Below you can find the class diagram of EiffelBase2, split into two hierarchies: containers and streams/iterators. All EiffelBase2 class names start with V_ (for Verified), but the prefix is omitted in the current document for brevity.

In the diagrams below asterisk and italics font indicates a deferred class. Lighter fill color indicates that the class provides an immutable interface to the data, in other words, it is impossible to modify the content of the container using this interface.

Container class hierarchy
Iterator class hierarchy

Usage examples

Immutable interfaces

With immutable interfaces you can give your clients read-only access to a container you store:

class TRAM_LINE
 
feature -- Access
 
    stations: V_SEQUENCE [STATION]
            -- Stations on the line.
            -- (Clients can traverse the stations, but cannot replace them, remove or add new ones.)
        do
            Result := station_list
        end
 
feature {NONE} -- Implementation
 
    station_list: V_LIST [STATION]
            -- List of line stations.
end

Iterators

Here is how you can iterate through any container:

do_something (container: V_CONTAINER [INTEGER])
    do
        across
            container as i
        loop
            print (i.item)            
        end
    end

The same thing using the explicit syntax:

do_something (container: V_CONTAINER [INTEGER])
    local
        i: V_ITERATOR [INTEGER]
    do
        from
            i := container.new_cursor
        until
            i.after
        loop
            print (i.item)
            i.forth
        end
    end

Here is some more advanced stuff you can do with lists:

do_something (list: V_LIST [INTEGER])
    local
        i: V_LIST_ITERATOR [INTEGER]
    do
        -- Find the last 0 at or before position 5:
        list.at (5).search_back (0)
        -- Find the first positive element at or after position 5:
        i := list.at (5)
        i.satisfy_forth (agent (x: INTEGER): BOOLEAN do Result := x > 0 end)
        -- And insert a 0 after it:
        i.extend_right (0)
    end

Sets and tables

Here is how you create and use a simple hash table (keys must inherit from HASHABLE):

do_something
    local
        table: V_HASH_TABLE [STRING, INTEGER]
    do
        create table.with_object_equality
        table ["cat"] := 1
        table ["dog"] := 2
        print (table ["cat"] + table ["dog"])
        -- Prints "3"
    end

If you need a custom hash function or equivalence relation on keys, you can use V_GENERAL_HASH_TABLE, for example:

do_something
    local
        table: V_GENERAL_HASH_TABLE [STRING, INTEGER]
    do
        -- Create case-insensitive table:
        create table.make (
            agent {STRING}.is_case_insensitive_equal,
            agent (s: STRING): INTEGER do Result := s.as_lower.hash_code end
        )
        table ["cat"] := 1
        table ["dog"] := 2
        print (table ["CAT"] + table ["dog"])
        -- Prints "3"
    end

Similar style applies to V_HASH_SET and V_GENERAL_HASH_SET, V_SORTED_TABLE and V_GENERAL_SORTED_TABLE, V_SORTED_SET and V_GENERAL_SORTED_SET.

Sometimes you want to use an object as a key in a table, but the content of the object can change, so you cannot derive a hash code (or order) from any of its attributes. EiffelBase2 provides a utility class V_REFERENCE_HASHABLE, which derives a hash code from the object's identity rather than its content. For example:

class 
    CAR
inherit 
    V_REFERENCE_HASHABLE
 
feature -- Access
    color: COLOR
 
    location: POINT
 
feature -- Modification
...
end
 
 
class GUI_APPLICATION
 
feature
    cars: V_HASH_TABLE [CAR, CAR_VIEW]
            -- Cars in the city and their graphical views.
 
    escape_police (c: CAR)
            -- Drive `c' away, repaint it and update its view.
        require
            car_in_city: cars.has_key (c)
        do
            c.move (100, 100) -- change location
            c.repaint (white) -- change color
            cars [c].update -- ... but still can be used to access its view
        end
end

Stream piping

Iterators in EiffelBase2 are a special case of streams. Sometimes you can avoid writing a loop by piping an input stream into an output stream, for example:

do_something
    local
        array: V_ARRAY [INTEGER]
    do
        create array.make (1, 10)
        -- Fill array with random integers:
        array.new_cursor.pipe (create {V_RANDOM})
        -- Fill array with values parsed from a string:
        array.new_cursor.pipe (create {V_STRING_INPUT [INTEGER]}.make ("1 2 3 4 5 6 7 8 9 10", agent {STRING}.to_integer))
        -- Print array elements into standard output:
        (create {V_STANDARD_OUTPUT}).pipe (array.new_cursor)
        -- Prints "1 2 3 4 5 6 7 8 9 10 "
    end

Model-based contracts

Models

EiffelBase2 is specified using the model-based contracts specification method. This method prescribes that each class defines a model - a mathematical object that represents explicitly its abstract state space. The model of a class is expressed as tuple of one or more predefined mathematical objects: booleans, integers, sets, relations, maps, sequences or bags. We also introduce a special mathematical sort for object IDs (references).

Such mathematical objects in the program are represented by model classes, which are immutable and thus are straightforward translations of mathematical definitions. The Mathematical Model Library (MML), which is a part of EiffelBase2 contains model classes for sets, relations, maps, sequences and bags. Boolean and integer components of models are represented by standard classes BOOLEAN and INTEGER. Finally, arbitrary reference classes can be used as model components to denote the mathematical sort of references.

For example, a mathematical sequence of elements is a model for a stack or a queue. A triple consisting of a reference to the target container, the sequence of elements and an integer is a model for an iterator.

The value of each component of the model is defined by a model query. You define the model of the class in the source code by listing its model queries under the tag model in the class's note clause. For example:

note
    model: target, sequence, index
 
class V_LIST_ITERATOR [G]
 
feature -- Access
 
    target: V_LIST
            -- List to iterate over.
        deferred
        end
 
    index: INTEGER
            -- Current position.
        deferred
        end
...
feature -- Specification
    sequence: MML_SEQUENCE [G]
            -- Sequence of elements in `target'.
        note
            status: specification
        deferred
        end
end

Here we declared the model of class V_LIST_ITERATOR consisting of tree components: a reference (to V_LIST), a mathematical sequence of type MML_SEQUENCE and an integer index. As you can see, model queries are not necessarily introduced specifically for specification purposes (as is the case with sequence); regular queries meant to be called from the code can be also reused as model queries (as target and index in this example). We attach a status: specification note to a query to indicate that its primary purpose is specification. Those annotations can be used by tools to check, if a specification-only feature accidentally got called from non-specification code, and to remove such features from the code before compiling it.

Contracts

The purpose of introducing model queries is to define the postconditions of regular features in their terms. For queries we define their result (or the model of the result, in case the query returns a fresh object) as a function of the model of Current and (the models of) the arguments. For commands we define their effects on the model queries of Current and the arguments. We also supply them with modify clauses that list all the model queries whose values are allowed to be changed by the command. These clauses can be used by tools to generate additional postconditions m = old m for each model query m of Current and other arguments that is not mentioned in the modify clause.

The model-based contracts approach does not constrain the way in which you write preconditions: it is not necessary to express them through model queries if they can be conveniently expressed otherwise.

Class invariants in the model-based contracts approach constrain the values of model queries to make them reflect precisely the abstract state space of the class.

Let us add a couple of features and model-based contracts into the class V_LIST_ITERATOR shown above:

note
    model: target, sequence, index
 
class V_LIST_ITERATOR [G]
 
feature -- Access
 
    item: G
            -- Item at current position.
        require
            not_off: not off
        deferred
        ensure
            definition: Result = sequence [index]
        end
...
 
feature -- Status report
 
    off: BOOLEAN
            -- Is current position off scope?
        deferred
        ensure
            definition: Result = not sequence.domain [index]
        end
...
 
feature -- Extension
 
    extend_right (v: G)
            -- Insert `v' to the right of current position.
            -- Do not move cursor.
        note
            modify: sequence
        require
            not_off: not off
	deferred
	ensure
	    sequence_effect: sequence |=| old (sequence.front (index) & v + sequence.tail (index + 1))
	end
...
 
invariant
    index_in_range: 0 <= index and index <= sequence.count + 1
end

Inheritance

If a class B inherits from A it is free to choose, whether to reuse each of A's model queries to represent its own model, as well as introduce new model queries. If B does not reuse an A's model query is has to provide a linking invariant: a definition of the old model query in terms of B's model. Linking invariants explain the parent's model in terms of the heir's model and thus make sure that the inherited model-based contracts make sense in the heir.

For example, look at V_SET, which inherits directly from V_CONTAINER:

note
    model: bag
class V_CONTAINER [G]
...
end
 
note
    model: set
class V_SET[G]
...
invariant
    ...
    bag_domain_definition: bag.domain |=| set
    bag_definition: bag.is_constant (1)
end

Here the linking invariant, provided as part of the class invariant in V_SET completely defines an old model query bag in terms of a new model query set.

Status and roadmap

EiffelBase2 is currently being developed as a project at ETH Zurich.

It has been used in the following projects: