A Timber program consists of a collection of modules. One of these is the root module and contains the root definition. The root definition must have a prescribed root type, that may depend on the target environment. A Timber installation may support several target environments/platforms.
The other modules in the program provide auxiliary definitions, used by the root module and by other auxiliary modules. The compilation unit in Timber is the module, so modules may be compiled individually (but see below for dependencies).
module moduleName where
importDeclaration*
topLevelDeclaration*
private
topLevelDeclaration*
Here, as in many places of Timber syntax, indentation is significant. The import and top-level declarations must be indented at least one step and be vertically aligned, i.e., the first character of each declaration must be in the same column. An exception is the (common) case where there is no private part; then indentation can be omitted (and all declarations start in the leftmost column).
A module has a simple name, which is an identifier starting with an upper-case letter. Modules are conventionally stored in files of the same name with suffix .t, i.e. module Dictionary is stored in Dictionary.t.
Projects and Timber sites may use hierarchical module names to allow multiple modules with the same name or to indicate structure e.g. in a library. Thus, module Dictionary may need to be referred to by clients as e.g. Data.Object.Dictionary; it is up to the implementation whether this module is stored in Data.Object.Dictionary.t or in Data/Object/Dictionary.t relative to some installation-dependent notion of search paths. Module hierarchies in Timber have no other significance than to support organizing and subsequently finding modules in a file system.
The module name is given in the header, as described above.
A module may depend on other modules, i.e. use types or values defined in these modules. In a Timber program the dependency graph between modules must be acyclic; no recursive dependencies are allowed. This means that modules can be compiled in dependency order: when a certain module is compiled, all the modules it depends on have already been compiled. A compiler option can make the compiler decide on recompilation order after a system has been changed.
Following the header is a sequence of import/use declarations, i.e. declaration of the modules that the current module depends on. There are two forms of such declarations:
Both forms of declaration give access to all exported entities from the named module; the difference is that in the latter case one must use the qualified name of an imported entity, while in the former case the simple (unqualified) name is enough, unless name clashes occur.
Name clashes are handled as follows:
All the entities (types, defaults and values) defined in the sequence of public top-level declarations are exported, i.e. can be referred to by importing/using modules. Entities defined in the private part are not exported and hence not visible to importing/using modules. In particular, this means that the type environment of the public part must be closed, i.e. the type of an exported value must not mention a type defined in the private part. Imported entities are re-exported, i.e.the import relation is transitive: if module C imports B, which imports A, the entities in A are visible in C, even if C does not explicitly import A. If the only chain from module C to module A passes a "use" declaration, though, entities defined in A must be referred to in C by their qualified names.