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. It represents a single dictionary object only:

/*
 * Pseudocode for a dictionary module. Usage:
 *
 *     Dictionary.put("mega", "either 1000000 or 1048576");
 *     print(Dictionary.numberOfEntries());
 */
module Dictionary {
    import capacity, String;
    export put, get, numberOfEntries;
    type Entry = { const key: String; value: String; }
    var entries: [Entry] = [Entry](capacity);
    var size: int = 0;
    func put(key: String, value: String) { ... }
    func get(key: String) -> String { ... }
    func numberOfEntries() -> int { 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 pseudocode dictionary module exporting a dictionary type. Usage:
 *
 *     DictionaryModule.Dictionary d1, d2, d3;
 *     Dictionary.put(d2, "mega", "either 1000000 or 1048576");
 *     print(numberOfEntries(d3));
 */
module DictionaryModule {
    import String;
    export Dictionary, put, get, numberOfEntries;
    type Entry = { const key: String; value: String; }
    type Dictionary = {
        const capacity: int;
        entries: [Entry] = [Entry](capacity);
        size: int = 0;
    }
    func put(d: Dictionary, key: String, value: String) {...}
    func get(d: Dictionary, key: String) -> String {...}
    func numberOfEntries(d: Dictionary) -> int { 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:
 *
 *     var d1, d2, d3: Dictionary;
 *     d2.put("mega", "either 1000000 or 1048576");
 *     print(d3.numberOfEntries());
 */
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;}
}
Exercise: Would an interface with only static methods work? Why or why not?

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!

// THIS IS OLD JAVASCRIPT: Don't write like this anymore.
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

Summary

We’ve covered:

  • Purpose of Modules
  • Information hiding
  • The module-as-object pattern
  • The module-as-manager pattern
  • The module-as-type pattern
  • Scope issues
  • Modules in different programming languages