Keris
| Home Quick Tour Part 1: Basics Part 2: Extensibiliy Part 3: Class Abstractions Archive
| Quick Tour |
Part II: Extensibility |
This page will introduce features related to extensibility. With these features it is possible to evolve existing modules safely without introducing incompatibilities or inconsistencies.
3 Refining Modules
| Keris has support for non-invasive extensions through a module refinement mechanism. This mechanism allows to refine an existing module by providing new functionality or by overriding existing functionality. The type system guarantees that the refined version of a module is backward compatible to the original version. We could, for instance, imagine to improve our module `MATHS` by making the `sqrt` function more precise (and consequently less efficient). In the example below, we derive a new version of `MATHS` called `NUMATHS` which overrides function `goodEnought` accordingly. Furthermore, module `NUMATHS` also provides more functionality by offering a new function `log`.
    module NUMATHS refines MATHS {
        private boolean goodEnough(float guess, float x) {
            return abs(sqr(guess) - x) < 0.0001f;
        }
        float log(float x) {
            return (float)Math.log(x);
        }
    }
Module refinements like `NUMATHS` inherit the interface and the member implementations from their parent module and therefore also implement the module interface of their parent module. Furthermore, refinements also inherit the submodules and the context dependencies specified in the parent module. In Keris, backward compatibility of module refinements implies substitutability; i.e. for the example above, it would be generally legal to replace module `MATHS` with module `NUMATHS`. This is exactly what happens in the next program. Here, we refine our module `GEOAPP` by replacing module `MATHS` with module `NUMATHS`. We do this by covariantly overriding the corresponding submodule declaration.
    module NUGEOAPP refines GEOAPP {
        module NUMATHS;
    }
The new module `NUGEOAPP` offers the same functionality like `GEOAPP` but does calculations with a higher precision. Like `GEOAPP`, it is not executable, since it also depends on an `INOUT` module. Please note that in the body of `NUGEOAPP`, we could access the `NUMATHS` submodule via both module names, `MATHS` and `NUMATHS`. The only difference is that when accessed via `NUMATHS`, we could refer to the new function `log`. In a final step we evolve our former `MAIN` module by incorporating the new `NUGEOAPP` component. The refined `MAIN` module is executable, but now uses our newly developed versions of former modules.
    module NUMAIN refines MAIN {
        module NUGEOAPP;
    }
This small example demonstrates that Keris' module assembly and refinement mechanism supports the extension of atomic modules as well as extensions of fully linked programs (represented by modules with aggregated submodules). Extension of fully linked systems do not require to establish module interconnections again; the fully linked program structure is reused and only the submodules and functions to replace or add have to be specified. This extensibility mechanism features plug-and-play programming. It does not touch existing code. Therefore, the former module versions do not get lost in the evolution process. We could even assemble a system that makes use of both old and new modules without having to fear unpredictable interferences.
4 Specializing Modules
| Refining a module is the process of extending a module by adding new functionality or by modifying existing functionality through overriding. A module refinement yields a new version of an existing module. This new version subsumes the old one in the sense that it is backward compatible to the old version. It is always possible in Keris to replace a module with one of its refinements. In the following code, module `BUGGYMOD` aggregates a submodule that subsumes another submodule. In other words, `BUGGYMOD` defines a context in which two different versions of logically one module are present. Since references to submodule `SORTER` are ambiguous within module `BUGGYMOD`, the type system of Keris considers this case to be illegal.
    module BUGGYMOD {
        module MATHS;
        module NUMATHS;
    }
Sometimes we would like to evolve a module and use the evolved version side by side with the original one. For this case, we need a different form of reuse: We would like to define a new module on top of on an existing one, but we do not want the new module to subsume the old one: it should be logically distinct. We call this process of creating a new distinct module which reuses the definition of an existing module specialization. Imagine we would like to develop a math module which has similar functionality than `NUMATHS`, but offers a logarithm function to the base 10 instead of e. Implementing such a module as a refinement would be clearly not desirable due to the change in semantics which could easily break existing code that relies on the contract that `NUMATHS.log` is the natural logarithm.
    module MYMATHS specializes NUMATHS {
        float log(float x) {
            return super.log(x) / super.log(10);
        }
    }
Module `MYMATHS` inherits all members and requirements from `NUMATHS` and overrides function `log`. The example shows that in `MYMATHS` it is still possible to refer to the former implementation via the keyword `super`. As a specialization of `NUMATHS`, module `MYMATHS` is not required to be backward compatible to its parent module. In particular, it neither subsumes module `NUMATHS` nor implements any of the module interfaces that are implemented by module `NUMATHS`. This restriction turns `NUMATHS` and `MYMATHS` into completely different modules. Consequently, it is perfectly legal to define a module with both a `NUMATHS` and a `MYMATHS` submodule.
    module WELLFORMED {
        module NUMATHS;
        module MYMATHS;
    }
Often, mutual referential modules have to be specialized at the same time consistently. The ability to refer to a specialized version of a module requires that we are able to specialize context dependencies as well. This rewiring is expressed in the following code using the `as` operator.
    module MYGEO specializes GEO requires MYMATHS as MATHS {
        float one() {
            return MYMATHS.log(10);
        }
    }
The `MYGEO` module specializes module `GEO` and instead of requiring the original `MATHS` module, it now refers to the specialized version `MYMATHS`. While module refinements promote the substitutability of modules, module specializations support the notion of conceptual abstraction on the module level. Conceptual abstraction refers to the ability to factor out code and structure shared by several modules into a common parent module which gets specialized independently into different directions. The specializations represent distinct modules that cannot be substituted for the common parent module.
[Part I] [Part III] |