Keris
Home
Quick Tour
   Part 1: Basics
   Part 2: Extensibiliy
   Part 3: Classes


Quick Tour
Part III: Class Abstractions

Parts I and II of this tour only considered functions as members of modules. Of course, it is also possible to define other module members such as variables and classes. This final part discusses briefly the significance and use of classes in Keris.

 5  Class Interfaces and Implementations
The essence of most module systems consists in a separation between interfaces and implementations to enable true modularity in combination with separate compilation. Keris uses this principle as an abstraction mechanism even for classes. It destinguishes between class interfaces, class implementations and class fields, which associate the two.

The following program defines an interface for modules that provide a Point class abstraction. It defines an interface IPoint which specifies a signature consisting of two methods getX() and getY(), and an object constructor. The second declaration states that modules implementing POINTS also have to define a class field which implements this interface.

module interface POINTS {
    interface IPoint {
        IPoint(int x, int y);
        int getX();
        int getY();
    }
    class Point implements IPoint;
}

We now look at a concrete implementation of POINTS. Module MYPOINTS below defines a class implementation CPoint that implements IPoint (which gets inherited from the implemented module interface POINTS). Furthermore it defines class field Point by separately specifying its interface and implementation: The interface is given by IPoint whereas CPoint acts as the concrete implementation (or the runtime representation of class field Point).

module MYPOINTS implements POINTS requires INOUT {
    class CPoint implements IPoint {
        int x, y;
        public CPoint(int x, int y) {
            this.x = x;
            this.y = y;
        }
        public int getX() {
            return x;
        }
        public int getY() {
            return y;
        }
    }
    class Point implements IPoint = CPoint;
    Point root() {
        return new Point(0, 0);
    }
    void print(Point p) {
        INOUT.write("(" + p.getX() + ", " + p.getY() + ")");
    }
}

The implementations of the functions print and root show that class fields behave just like regular Java classes: They define new types, they can be instantiated, and members of corresponding objects can be accessed. The main difference to regular Java classes is that class fields are virtual and therefore can be covariantly overridden in refined modules. Covariant overriding of class fields includes the extension of the set of implemented interfaces as well as the ability to specify a new class field implementation.

Here is another module interface that describes a possible color aspect of points. Please note that this new module interface COLORPOINTS is not at all related to POINTS. It simply also specifies a class field Point, which is, this time, constrained by interface IColor.

module interface COLORPOINTS requires COLOR {
    interface IColor {
        void setColor(COLOR.Color color);
        COLOR.Color getColor();
    }
    class Point implements IColor;
}

We now refine the original MYPOINTS module by additionally implementing the new COLORPOINTS module interface. Please note that MYCOLORPOINTS implicitly requires module COLOR since its implemented module interface MYPOINTS does so.

module MYCOLORPOINTS refines MYPOINTS implements COLORPOINTS {
    import COLOR.*;
    class CCPoint extends CPoint implements IColor {
        Color color;
        public CCPoint(int x, int y) {
            super(x, y);
        }
        public void setColor(Color color) {
            this.color = color;
        }
        public Color getColor() {
            return color;
        }
    }
    class Point implements IPoint, IColor = CCPoint;
    void print(Point p) {
        super.print(p);
        INOUT.write(" col = " + p.getColor());
    }
}

Refinement COLORPOINTS specifies that class field Point now also supports the IColor interface and is implemented by the CCPoint class. Furthermore, print is overridden to include the color in the output. At this point, one might wonder what happens to method root of the original module POINTS which instantiates class field Point. In fact, for the refined module it now returns a colored point since we were overriding class field Point. The ability to covariantly refine types (or class fields in our case) is essential for extending object-oriented software. Most object-oriented languages support interface and implementation inheritance. But inheritance alone does not support software refinement or software specialization well. Existing code refers to the former type and often cannot be overridden covariantly in a type-safe way to allow access to extended features.

 

 6  Class Dependencies
The previous section explained how to declare and how to evolve class abstractions. The presented mechanism does not allow to relate different class fields to each other; every class field defines an own independent interface/implementation pair. We need a mechanism similar to subclassing/subtyping that introduces dependencies between class fields. Since implementation inheritance is difficult to handle modularly (with full abstraction), Keris only supports the explicit declaration of subtype relationships between class fields. The following code illustrates this.

module SHAPES requires POINTS {
    interface IShape {
        boolean inShape(POINTS.Point pt);
    }
    abstract class Shape implements IShape;
    private java.util.Set shapes = new java.util.HashSet();
    void registerShape(Shape s) {
        shapes.add(s);
    }
}
module BOXES requires SHAPES, POINTS {
    import POINTS.*;
    import SHAPES.*;
    interface IBox {
        IBox(Point topleft, Point botright);
    }
    class Box extends Shape implements IShape, IBox = {
        Point topleft, botright;
        public Box(Point topl, Point botr) {
            topleft = topl;
            botright = botr;
            SHAPES.registerShape(this);
        }
        public boolean inShape(Point pt) {
            return pt.getX() > topleft.getX() &&
                   pt.getX() < botright.getX() &&
                   pt.getY() > topleft.getY() &&
                   pt.getY() < botright.getY();
        }
    }
}

In this program, we define an abstract class field Shape within module SHAPES. Abstract class fields are like abstract classes: They simply define types, and cannot be instantiated. Thus, there is no need to specify implementations for abstract class fields. Module BOXES defines a class field Box which extends SHAPES.Shape. This extends declaration defines Box to denote a subtype of Shape requiring that Box implements at least all the interfaces implemented by Shape. The type checker has to make sure that it is not possible to link refinements of BOXES and SHAPES where this invariant is broken. Thus, such subtype dependencies between class fields promote the consistent refinement or specialization of class field hierarchies.

Here is an example which successfully links modules BOXES and SHAPES in the context of module LEGAL.

module LEGAL requires POINTS {
    module BOXES;
    module SHAPES;
}

The following "refinement" of module LEGAL is not well-formed, because it breaks the dependency established between the class fields Shape and Box.

module NUSHAPES refines SHAPES {
    interface INuShape {
        void scale(int factor);
    }
    abstract class Shape implements IShape, INuShape;
}
module ILLLEGAL refines LEGAL {
    module NUSHAPES;
}

[Part I] [Part II]