CS 334 Lecture 16

CS 334 Lecture 16

Contents:

    1. Eiffel
      1. The inherit clause
      2. Summary of assertions:
      3. Constrained genericity.
Read chapters 1-8 of Switzer (Eiffel: An introduction). Read 1-5 ASAP, and rest over next week.

Tutorials on the language and the IDE, EiffelBench, are available through the CS334 web page. Please read both and do the EiffelBench tutorial by Tuesday.

Eiffel

We will be using the OO language, EIFFEL3.

It is one of best on the market, though it has some important flaws.

To use Eiffel, type:

    source EiffelSetup
    ebench
You can follow ebench with "&" if wish to spawn it off as a separate process, but then programs can't get keyboard input when running in the debug system.

If have problems starting up ebench, try typing:

    echo $EIFFEL3 $PLATFORM
to make sure that EiffelSetup worked properly.

Let's start with a very simple example consisting of 3 classes. The following should be stored in a file, point.e.

All sample programs can be found in ~kim/cs334stuff/Eiffel

class POINT 
-- class which suports a movable point

creation    -- designates a method which may be used when 
                -- creating a POINT object.
    Create
-- Note:  When an object is defined it sets all attributes to 
--  default value for that type (e.g. 0 for integer and 
--  real).  Can also designate one or more features which can 
--  be called to do further initializations

feature

    Create (lp: LINKED_STACK [POINT]) is
            -- Create point at origin and push it onto `lp'
        require
            lp /= Void
        do
            lp.put (Current)  -- Current is name for "self", the                                    -- object executing the method.
        end; -- Create
                
    x, y: REAL;

    translate (a, b: REAL) is
            -- Move by `a' horizontally, `b' vertically.
        do
            x := x+a;
            y := y+b
        end; -- translate

    scale (factor: REAL) is
            -- Scale by a ratio of `factor'.
        do
            x := factor * x;
            y := factor * y
        end; -- scale

    display is
            -- Output position of point
        do
            io.putstring ("Current position: x = ");
            io.putreal (x);
            io.putstring ("; y = ");
            io.putreal (y);
            io.new_line -- writeln
        end -- display

end -- class POINT
Note that "io" is a feature of every class (automatically inherited from ANY) which provides io features. Simply send messages to it to do I/O

In a separate file: interaction.e:

class INTERACTION 
-- simple program demonstrating creation and handling of 
-- requests in Eiffel

creation
    create  -- while name is the same as Point, it need not be!

feature {NONE}  -- NONE means these are inaccessible outside of INTERACTION
    
    my_point: POINT; 

    request: INTEGER; 

    Up, Down, Left, Right, Quit: INTEGER is unique;  
                            -- equivalent of user-defined type
    
    point_stack: LINKED_STACK [POINT];  -- from library

feature -- since no qualifier, these are public

    over: BOOLEAN;

    Create is
            -- Create a point
        do
            !!point_stack.make; -- create and execute "make".
            !!my_point.Create (point_stack);
        end; -- Create

    get_request is
            -- Ask what the user wants to do next,
            -- returning the answer in attribute `request':
            -- `Up', `Down', `Left', `Right' or `Quit'.
        local
            answer: CHARACTER; 
            correct: BOOLEAN
        do
                        -- all loops are of from .. until .. loop form
            from        -- anything after from is initialization
                        -- correct := false <- automatically set!
            until   -- continue until the following cond'n true!
                correct
            loop
                io.new_line;
                io.putstring ("Enter command (one character)");
                io.new_line;
                io.putstring ("U for Up, D for Down, L for Left, %
                            %R for Right, Q for Quit: ");
                    -- % indicates continue string to next line
                io.readchar; 
                answer := io.lastchar;
                io.next_line;
                correct := true;

                inspect -- inspect is like a case statement.
                    answer
                when 'u', 'U' then
                    request := Up
                when 'd', 'D' then 
                    request := Down
                when 'l', 'L' then
                    request := Left
                when 'r', 'R' then 
                    request := Right
                when 'q', 'Q' then
                    request := Quit
                else
                    io.new_line;
                    io.putstring ("Bad code. Please enter again.");
                    io.new_line;
                    correct := false
                end
            end
        end; -- get_request
            
    one_command is
            -- Get user request and execute it
        do
            get_request;
            inspect request
            when Up then
                my_point.translate (0., 1.)
            when Down then 
                my_point.translate (0., -1.)
            when Left then 
                my_point.translate (-1., 0.)
            when Right then 
                my_point.translate (1., 0.)
            when Quit then 
                over := true 
            end;
            my_point.display
        end -- one_command

end -- class INTERACTION
Finally, the class which is executed as the main program:

class SESSION 

creation
    Create

feature

    Create is
            -- Execute sequence of interactive commands
        local
            interface: INTERACTION
        do
            from
                !!interface.Create
            until
                interface.over
            loop
                interface.one_command
            end
        end -- Create

end -- class SESSION

This is all controlled by the following "ACE"

system
    TRY_EIFFEL
    -- Replace SYSTEM_NAME by the name of the executable file
    -- to be generated for your system.

root

    SESSION (ROOT_CLUSTER): "create"
    -- Replace ROOT_CLASS, ROOT_CLUSTER and creation_procedure
    -- by the names of the root class, root class cluster and
    -- root creation procedure for your system.
    -- The `(ROOT_CLUSTER)' part may be omitted if there is
    -- no other class of name ROOT_CLASS in the universe.

default

    assertion (require);
    precompiled ("$EIFFEL3/precompiled/spec/$PLATFORM/base")

cluster

    ROOT_CLUSTER:   ".";
        -- Replace ROOT_CLUSTER and PATH by the names of the
        -- root class cluster & path for your system.
        -- Add any other clusters that your system will need.

        kernel:             "$EIFFEL3/library/base/kernel";
        support:            "$EIFFEL3/library/base/support";
        access:             "$EIFFEL3/library/base/structures/access";
        cursors:            "$EIFFEL3/library/base/structures/cursors";
        cursor_tree:        "$EIFFEL3/library/base/structures/cursor_tree";
        dispenser:          "$EIFFEL3/library/base/structures/dispenser";
        iteration:          "$EIFFEL3/library/base/structures/iteration";
        list:               "$EIFFEL3/library/base/structures/list";
        obsolete:           "$EIFFEL3/library/base/structures/obsolete";
        set:                "$EIFFEL3/library/base/structures/set";
        sort:               "$EIFFEL3/library/base/structures/sort";
        storage:            "$EIFFEL3/library/base/structures/storage";
        table:              "$EIFFEL3/library/base/structures/table";
        traversing:         "$EIFFEL3/library/base/structures/traversing";
        tree:               "$EIFFEL3/library/base/structures/tree";

end
Here is another example from Eiffel using generics to support ordered pairs and rationals:

deferred class ORDERED_PAIR2 [T] 
-- this class cannot be instantiated because method display 
-- is deferred!
    feature
        x : T;  -- first coordinate
        y : T;  -- second coordinate

        setx(r : T) is
            -- set first coordinate
            do
                x := r
            end; -- setx

        sety(r : T) is
            -- set second coordinate
            do
                y := r
            end; -- sety

        display is
            -- display the ordered pair
            deferred    -- must be filled in in subclass
            end; -- display

        same(other : like Current) : BOOLEAN is
        do
            Result := (x = other.x) and (y = other.y)
        end -- same
end -- ORDERED_PAIR2

Return answer from function by assigning to "Result".

When use must instantiate T, see NEWRATIONAL below. Must also instantiate any deferred features (e.g., display) before can use.

Subclasses and Inheritance

A new class can be declared to be a subclass of any other class. The new class then "inherits" all features of the old class (think of this as almost like copying the code for all features of old class into the new class).

The new class can add new features or redefine old ones.

class NEWRATIONAL 

inherit
    ORDERED_PAIR2 [INTEGER]
        rename  x as n, -- can change names of features
                y as d
        redefine same   -- indicates that same will be redefined.  
                -- Need not mention display since it was deferred!
        end

creation Create

feature

    Create is
            -- create a rational
        do
            d := 1
        end; -- Create

feature {NONE}  -- private method
    reduce : INTEGER is
            -- reduce to lowest terms
        local
            num,den,next : INTEGER
        do
            if (n =0) or (d = 0) then
                Result := 1
            else
                if n < 0 then num := -n else num := n end;
                if d < 0 then den := -d else den := d end;

                from
                    next := num \\ den  -- \\ is mod operator
                invariant   -- must be true each time through loop
                    ((num \\ next) = 0) and ((den \\ next) = 0)
                variant -- must decrease each time through loop
                    next
                until
                    next = 0
                loop
                    num := den;
                    den := next;
                    next := (num \\ den)
                end;

                Result := den
            end
        end; -- reduce

feature

    set(numer, denom : INTEGER) is
            -- set the numerator and denominator
            -- post: d > 0
        require -- precondition
            denom /= 0
        local
            gcd : INTEGER
        do

            n := numer;
            d := denom;

            if d < 0 then
                n := -n;
                d := -d
            end;

            gcd := reduce;
            
            n := n // gcd;
            d := d // gcd
        ensure  -- postcondition
            d > 0
        end; -- set

    read is
            -- get rational in form n/d from input
        local
            num, den, attempts : INTEGER
        do
            io.readint;
            num := io.lastint;
            io.readchar;
            io.readint;
            den := io.lastint;
            set(num,den)
        ensure
            d > 0
        rescue  -- exception handler
            if attempts < 3 then
                io.next_line;   -- go to next input line
                io.new_line;    -- go to next output line
                io.putstring("A fraction is an integer ");
                io.putstring("divided by a non-zero integer.");
                io.putstring("  Enter a fraction: ");
                attempts := attempts + 1;
                retry
            end
        end; -- read
            
    display is
            -- display the fraction
        do
            if n = d*(n // d) then
                io.putint(n // d)
            else
                io.putint(n);
                io.putchar('/');
                io.putint(d)
            end
        end; -- display

    same(other : like Current) : BOOLEAN is
            -- are the fractions equal?
        do
            Result := (n*other.d = d*other.n)
        end; -- same

    lessthan(other : like Current) : BOOLEAN is
            -- is Current < other
        do
            Result := (n*other.d < d*other.n)
        end;

invariant
    d /= 0

end -- NEWRATIONAL
Note that "like Current" in "lessthan" denotes the class of the object receiving the message.

Can also use "like x" for x any instance variable of class.

Declaring class to be "like Current" helps ensure that routine will work properly in subclasses - guarantees class of argument same as class of object sending message to.

Can also have multiple inheritance (new class inherits from more than one class).

Can add capabilities to any NEWRATIONAL with a subclass:

class RATIONALMATH 

inherit
    NEWRATIONAL

creation
    Create

feature
    plus(other : like Current) : like Current is
        local
            sumnum, sumden : INTEGER;
        do
            sumnum := n*other.d + other.n*d;
            sumden := d*other.d;
            !!Result.Create;
            Result.set(sumnum,sumden)
        end; -- plus

-- add other operations here

end -- RATIONALMATH

A main program which uses RATIONALMATHis simply a class whose create procedure is the routine which is executed by the system.

class TESTRATIONAL

creation
    Create
feature

    Create is
        -- manipulate some rational numbers
    local
        p1,p2,p3 : RATIONALMATH
    do
        !!p1.Create;
        !!p2.Create;
        !!p3.Create;
        io.putstring("Enter a fraction as n/d: ");
        p1.read;
        io.putstring("Enter a fraction as n/d: ");
        p2.read;
        p1.display;
        io.new_line;
        p2.display;
        io.new_line;

        if p1.same(p2) then
            io.putstring("They're equal")
        else
            io.putstring("They're not equal")
        end;

        io.new_line;

    end -- Create

end -- TESTRATIONAL

"And", "or" are non-short-circuit. Use "and then", "or else" for short circuit.

Note that all variables should be thought of as references to objects.

Can't do anything to a variable until you either assign to it or create it!
(Illegal to send a message to an uninitialized variable.)

Thus a := b means that a now refers to the same object as b (sharing).

Important:: Note that only local variables or own attributes may be assigned to or created

To give "a" a new copy of object referred to by "b", write a := clone(b)
Note that you need not have created a before you can do this!

"a = b" is true iff a and b refer to the same object.

"equal(a,b)" is true iff the fields of a and b are identical.

Note that clone and equal are routines available in any class.
They are defined in class ANY which has features which are available to all other classes. ANY also includes io which was used earlier.

The inherit clause

Eiffel supports multiple inheritance (just list multiple names after INHERIT).

Leads to necessity of resolving name clashes.

As a result the INHERIT clause has a number of options (which must occur in the following order:

See section 5.5 - 5.8 for details on how to use these.

Few tips: Suppose feature m is defined in class A and have

class B 
    inherit A
        rename 
            m as k
        end;
feature ...

Now suppose that x : A, but at run time x actually holds a value of type B.

I.e., static type of x is A, but dynamic type is B.

By static type-checking, x.m should be defined. What is actually executed?

Answer: method k of B (only reasonable answer)

More complicated: Suppose you wish to redefine m in B, but use old definition in A as part of code:

class B 
    inherit A
        rename 
            m as old_m
        redefine m
        end;
feature 
    m (...) is
        do ... old_m ... end;

Unfortunately, this won't quite work. The problem arises again when we have a variable of static type A, holding a value of type B. What happens when x.m is executed?

As in the earlier example, the renamed version of m (in this case old_m) will be executed. This is not what we wanted. Instead must write:

class B 
    inherit A
        rename 
            m as old_m
    inherit A
        redefine 
            m
        select
            m
        end;
feature 
    m (...) is
        do ... old_m ... end;

Here we have actually inherited m twice. Since there are two definitions which can be used when this object is held in a variable of type A, we must tell the system which to use. The select clause tells it to resolve the ambiguity by taking the m from the second version (which is redefined in the class!).

Ugly, but it resolves all of the ambiguities!

I personally think multiple inheritance is usually a bad idea and should generally be avoided. (See comments later on multiple subtyping - which is helpful!)

Summary of assertions:

require: precondition - put before body of program

ensure: postcondition - put after body of program

invariant: for class - put at end of class

invariant: statement which is true every time through loop - put before body of loop

variant: positive integer expression which gets closer to zero every time through loop

check: assertion that can be put anywhere inside body of procedure

Different classes of assertion-checking can be turned on and off in the Ace file.

Assertions can be labelled - provides information in error messages and helpful in exception handling.

If method is redefined in subclass preconditions must be weaker (i.e., accept at least as many inputs as before) and postconditions must be stronger.

Therefore if use subclass in place of superclass, redefined method will accept any inputs that original would have and return results meeting expected criteria.

Constrained genericity.

Can write class which take class parameter which is a subclass of any specified class. For instance, can define (deferred) class ordered type as follows

deferred class ORDERED
feature
    value: ANY;
    lessthan(other: like value) is
        deferred;
end ORDERED;

Now define

class INTORD
feature
    value:INTEGER;
    lessthan(other:like value) is
        do 
            Result := Current < other.value
        end;

Can use in

class Sorting[T -> ORDERED] 
feature
    sort(thearray:ARRAY[T]):ARRAY[T] is
        local  ....
        do
            ......
            .... thearray.item(i).lessthan(thearray.item(j)) ....
        end;