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


Quick Tour
Part I: Basics

This page will give you a brief introduction to the syntax and semantics of Keris. It will explain how to develop and evolve modules, the basic building blocks of software written in Keris. The example code is complete and can be compiled using the Keris compiler keco. You can download a package containing all example files here. A file Module.keris is compiled with the following command: keco Module.keris.

 1  Defining Modules
In Keris, modules are the basic top-level abstractions for software components supporting separate compilation as well as function and type abstraction in an extensible fashion. Here is a small module MATHS that encapsulates a few arithmetical functions for floating-point numbers.

module MATHS {
    float abs(float x) {
        return (x < 0) ? -x : x;
    }
    float sqr(float x) {
        return x * x;
    }
    float sqrt(float x) {
        float guess = 1.0f;
        while (!goodEnough(guess, x))
            guess = (guess + x / guess) / 2.0f;
        return guess;
    }
    private boolean goodEnough(float guess, float x) {
        return MATHS.abs(sqr(guess) - x) < 0.01f;
    }
}

MATHS exports all public functions; private functions cannot be accessed from clients of MATHS. Functions without modifiers are public by default. The example also shows that functions of the own module can be accessed either in unqualified form, or by qualifying the function name with the module name.

We can now write a module that makes use of the functionality provided by module MATHS. In the following example we implement a function distance which calculates the geometric distance between two points in a 2-dimensional space. The dependency on module MATHS is expressed by explicitly stating MATHS in the requirement clause of our new module GEO. In addition to MATHS, we have a second dependency on a module INOUT.

module GEO requires MATHS, INOUT {
    float distance(float x1, float y1, float x2, float y2) {
        return MATHS.sqrt(MATHS.sqr(x1 - x2) + MATHS.sqr(y1 - y2));
    }
    void print(float x, float y) {
        INOUT.write("(" + x + ", " + y + ")");
    }
}

Again, we access members of required modules by qualifying their name with the corresponding module name. Since this can be cumbersome, Keris provides means to import members of other modules, so that they can be accessed without explicit qualification. Here is an alternative implementation that imports all members of MATHS into the scope of GEO.

module GEO requires MATHS, INOUT {
    import MATHS.*;
    float distance(float x1, float y1, float x2, float y2) {
        return sqrt(sqr(x1 - x2) + sqr(y1 - y2));
    }
    void print(float x, float y) {
        INOUT.write("(" + x + ", " + y + ")");
    }
}

It remains to show a specification of module INOUT. We do this by defining a module interface that specifies the signature of this module. Such a module interface does not contain code, it only specifies the types of members provided by a concrete implementation of this module.

module interface INOUT {
    String read();
    void write(String str);
}

We will now define a module CONSOLE that implements this interface and thus is a possible candidate for being used together with module GEO which requires an implementation of INOUT.

module CONSOLE implements INOUT {
    String read() {
        try {
            return new java.io.DataInputStream(System.in).readLine();
        } catch (java.io.IOException e) {
            return null;
        }
    }
    void write(String str) {
        System.out.print(str);
    }
}

This module implements the functions read and write by forwarding the calls to appropriate methods of the standard Java API for text in- and output on a terminal. It is of course possible to have alternative implementations for INOUT, as well as one module implementing multiple module interfaces. Please note that module GEO and MATHS do not explicitly implement a module interface. This is not strictly necessary since every module declaration implicitly defines a module interface of the same name with all public members. Nevertheless, the separation of module implementations from interfaces is important for the following reasons:

  • It enables separate compilation of recursively dependent modules,
  • it is a facility for hiding concrete representations of module members, and
  • it can be used as a vehicle for presenting different views of a single module implementation.
 

 2  Linking Modules
Keris distinguishes between modules and module instances. A module can be seen as a template for multiple module instances of the same structure and type. This distinction is necessary to allow a module to be deployed multiple times within a software system.

In Keris, modules are composed by aggregation; i.e. a Keris module may define a set of submodules (submodule instances would propably be the more exact term). It can access both required modules and submodules in its scope. The following example code implements a module GEOAPP which links modules MATHS and GEO by declaring both to be submodules. Submodule declarations start with the keyword module followed by the name of the module implementation.

module GEOAPP requires INOUT {
    module GEO;
    module MATHS;
    float readFloat() {
        while (true)
            try {
                return Float.parseFloat(INOUT.read());
            } catch (NumberFormatException e) {}
    }
    float pointDistance() {
        INOUT.write("x1 = ");
        float x1 = readFloat();
        INOUT.write("y1 = ");
        float y1 = readFloat();
        INOUT.write("x2 = ");
        float x2 = readFloat();
        INOUT.write("y2 = ");
        float y2 = readFloat();
        return GEO.distance(x1, y1, x2, y2);
    }
}

A module M can only aggregate a submodule S, if all modules required by S are accessible in the scope of M. A module N is accessible in the scope of M if N refers to M directly, or if N is required by M, or if N is a submodule of M. Therefore, the previous module is well-formed, since submodule MATHS has no context dependencies, and submodule GEO requires the modules MATHS and INOUT which are both accessible in GEOAPP.

Note that we do not refer to module MATHS directly in the body of GEOAPP. We introduced this submodule only to satisfy the requirements of submodule GEO. Alternatively to this aggregation, we could have also added MATHS to the requirements.

We now assemble a completely self-contained module MAIN (i.e. a module without context dependencies) which links module GEOAPP with an implementation of module INOUT.

module MAIN {
    module GEOAPP;
    module CONSOLE;
    void main(String[] args) {
        CONSOLE.write("distance = " + GEOAPP.pointDistance() + "\n");
    }
}

Modules without context dependencies which define a main method of the following signature: void main(String[] args), are executable. Modules get executed like Java classes. For running our MAIN module, one simply has to execute the command: java MAIN.

[Part II] [Part III]