Modules

In the 1970s modules were so novel and so cool, and now they pretty much come standard in most programming languages.

What are Modules?

Modules are structures that

Usage

In practice, modules are often used to define data abstractions using information hiding to safeguard its internal state.

Modules are more powerful than the static local variables of C, since modules spread state among several subroutines, not just one.

Here's the pseudocode for a data abstraction in the module-as-object style:

/*
 * A dictionary module.  Usage:
 *
 *     Dictionary.put("mega", "either 1000000 or 1048576");
 *     print(Dictionary.getSize());
 */
module Dictionary {
    import capacity, String;
    export put, get, getSize;
    struct Entry {String key; String value;}
    Entry[] entries = new Entry[capacity];
    int size = 0;
    void put(String key, String value) {...}
    String get(String key) {...}
    int getSize() {return size;}
}

In this module

So this module gives us only one dictionary. How can we get more? Answer: export a dictionary type! This style is called module-as-manager.

/*
 * A dictionary module exporting a dictionary type.  Usage:
 *
 *     DictionaryModule.Dictionary d1, d2, d3;
 *     Dictionary.put(d2, "mega", "either 1000000 or 1048576");
 *     print(getSize(d3));
 */
module DictionaryModule {
    import String;
    export Dictionary, put, get, getSize;
    struct Entry {String key; String value;}
    struct Dictionary(int capacity) {
        Entry[] entries = new Entry[capacity];
        int size = 0;
    }
    void put(Dictionary d, String key, String value) {...}
    String get(Dictionary, String key) {...}
    int getSize(Dictionary d) {return d.size;}
}

Notice how you compile the module only once, but you can declare many variables of the exported type.

The next technical advance is to make the module itself a type, rather than just a plain encapsulation device. This is the module-as-type style.

/*
 * A dictionary type.  Usage:
 *
 *     Dictionary d1, d2, d3;
 *     d2.put("mega", "either 1000000 or 1048576");
 *     print(d3.getSize());
 */
module Dictionary {
    import ...
    export ...
    ...
}

Languages that adopt this approach usually call this kind of module a class.

Classes have instances and modules do not.

Case Studies

Implementing Modules with Classes

In some languages, such as Java, there is no explicit module construct, but you can get the effect of the module with a class. But since classes have instances and modules don't, you need to be able to make a non-instantiable class. In Java you do this by making the constructor private, and all exported methods public static:

class Counter {
    private int data;
    private Counter() {}
    public static int up(int delta) {data += delta; return data;}
    public static int down(int delta) {data += delta; return data;}
}

Implementing Modules with Closures

In some languages, such as pre-ES6 JavaScript, there is no explicit module construct, but you can get the effect of the module with just functions!

var dictionary = function () {
    var data = {};
    return {
        put: function (k, v) {data[k] = v;},
        get: function (k) {return data[k];}
    };
}();

Modules and Scope

Modules are generally used with static scoping. They bring up the notions of open and closed scopes. Consider:

sub f {
    int x, y, z;

    sub g {
        int a, b, c;
        ...
    }

    module m {
        import x;
        export j;
        int i, j, k;
        sub h {print k;}
        ...
    }

    ....
}

In g, are x, y, and z automatically imported or not? Are a, b, and c automatically exported or not? What about in m?

The usual rule is that nothing is ever automatically exported from an inner scope, but when importing we have a choice:

Languages vary in how they handle scoping for modules and subroutines, for example:

LanguageSubroutinesModules
Modula optionally closed closed
Modula 2, 3 open closed
Ada open open
Euclid closed closed
Turing optionally closed ?
Clu closed closed
Exercise: Investigate and write an article on the scoping issues involved in:
  • "Opaque" import and export, as in Modula 2
  • Ada's private and limited private types
  • Generic modules (a.k.a. templates)
  • Modules split into separate specification and body parts (as in Ada)
  • Private and public parts of a package specification