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 functions, not just one.

There are three major uses for modules:

Module as Object
Module as Manager
Module as Type

Module as Object

Here's the pseudocode for a data abstraction in the module-as-object style. It represents a single dictionary object. Note how the module structure bundles the state and behavior together, and the import and export provide security through information hiding:

/*
 * 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! Let’s see that next.

Module as Manager

Here our module now contains and exports a type:

/*
 * 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.

Module as 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. Very few languages would consider such thing a module; instead, they would call it a class.

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

The thinking is that classes have instances and modules do not.

Simulating Modules

Some languages don't have modules, but you can simulate them with classes or closures.

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:

function f() {
    var x, y, z;

    function g() {
        var a, b, c;
        ...
    }

    module m {
        import x;
        export j;
        var i, j, k;
        function 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 functions, for example:

LanguageFunctionsModules
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

Recall Practice

Here are some questions useful for your spaced repetition learning. Many of the answers are not found on this page. Some will have popped up in lecture. Others will require you to do your own research.

  1. What beneficial capabilities to modules provide to a large program?
    Encapsulation, information hiding, separate compilation, selective import/export, reduced namespace pollution, and bug detection.
  2. How are modules more powerful than static local variables in C?
    Modules spread state among several functions, not just one.
  3. What are the three major uses for modules?
    Module as Object, Module as Manager, Module as Type
  4. When modules use keywords such as import and export, how do they indicate that certain entities are private?
    By not exporting them.
  5. When modules are top-level constructs, what can we say about their entities in terms of scope and lifetime?
    They are global in lifetime but not global in scope.
  6. What is meant by module-as-object?
    A module that represents a single object.
  7. What is meant by module-as-manager?
    A module that exports a type.
  8. The module-as-type construct is really just what?
    A class.
  9. What are two mechanisms to simulate modules in languages that don’t have them?
    Classes and closures.
  10. Java simulated modules with classes how?
    By making the constructor private and all exported methods public static.
  11. What is the difference between open and closed scope in the context of modules?
    Open scope imports from enclosing scope are automatic; closed scope requires explicit import.

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