Modules, shared

Shared Modules

A module can be imported more than once. If the module is shared, then there will only be a single instance of the imported module, and the code and the data that it defines will be shared by all of the importers. If the module is not shared, then each time it is imported, a separate instance of the code and the data will be created.

Both techniques have their uses:

  • Where a module maintains "local state"—a set of globals that might represent connections to the outside world, for example—you might want each instance (importation) of the module to have its own local state—its own connections—independent of the others. On the other hand, you might want all instances to share the local state—share a single connection to a database, for example.
  • Where a module does not maintain "local state", there's no need to have more than one copy of it.
  • Where a module defines a type, such as the float type, you'll probably want all users of the type to use the same type—so that its values can be interchanged.

The sharing/non-sharing distinction is determined by a module definition, not by its importers. You specify that a module is shared by following the keyword module at its head by shared as and a name, as in:

  module shared as com.stilo.math.float
  ; The rest of the floating point math. module.
  ; Shared because it has no local state, and
  ; because the "float" type needs to be common to all uses.

The name specified by shared as must either be a valid OmniMark name or a quoted string.

When a module is shared, the following apply:

  • Only the module source described by the first import of the shared module is used by the compiler. All other imports of modules with the same "shared as" name use the same compiled module, even if their source code is different from the first one.
  • If an opaque type is exported by a shared module, all its importers get the same type. opaque types exported from non-shared modules produce a distinct type for each import.
  • The process-start rules of a shared module are run as though they were declared where it is first imported. The process-end rules of a shared module are run as though they were declared where it is last imported (even though those process-end rules are taken from where the module is first imported). This ordering of things helps ensure that modules are initialized early enough, but not shut down too early.
  • A shared module isn't allowed to require anything. Because an instance of a shared module can be imported from more than one location, if it required anything, it would have to take what's supplied to it from one of its importers, and ignore what's supplied by other importations, and that would result in ambiguity and unexpected behavior.