Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Modules and Imports

Modules are the largest block of organization in MiniD.

Modules usually have a one-to-one correspondence with files. A single file holds a single module. Module names can also be hierarchical, a relationship that maps well to common file systems.

Usually one uses modules to organize large portions of code. For example, if you were writing a game, you might have several modules such as "ai", "camera", "items", "sound", etc. with each module defining functions and classes pertinent to those areas of the game scripting.

Modules

Module:
	ModuleDeclaration {Statement} EOF

ModuleDeclaration:
	[Decorators] 'module' Identifier {'.' Identifier} StatementTerminator

Modules declare a namespace into which all global declarations in the module are inserted. If you think of the global namespace in MiniD as a hierarchy, you have the root namespace in which lives the base library and any top-level namespaces. Each namespace can then have namespaces nested inside. So a module named "a.b" will be a namespace named "b" inside another namespace named "a", which is itself in the global namespace.

All modules must begin with a module declaration. This declares the name of the module, and if it has more than one part, where the module lives in the global hierarchy.

module a.b

MiniD is more strict than D with the naming of modules. If you name a module "a" but the source file is "b.md", when you perform an "import b" from another module, the interpreter will give you an error telling you that the filename and the module declaration names don't match. The naming is also case-sensitive.

When you have any global declaration in a module, that global is inserted into the module namespace for other modules to see. Local declarations are visible only within the module. This means at module scope at least, local and global parallel "public" and "private" in many other languages.

module a

// This will be seen as "a.x" from other modules.
global x = 5

// This is not visible to other modules.
local y = 10

Lastly, when you declare a module, you can also put decorators on it.

@attrs(
{
	author = "Fred Jones"
	date = "Feb 19, 2009"
})
module a

// blah blah blah..

When you do this, what will happen is that the decorators will be executed after the module's body has been evaluated, similar to how decorators on classes are evaluated after its members have been evaluated. Unlike other decorators, though, you cannot return a new value from the decorator that will be used in place of the module namespace. It just doesn't really make sense given how modules are loaded.

Imports

ImportStatement:
	'import' [Identifier '='] Identifier {'.' Identifier} [SelectiveImports] StatementTerminator
	'import' [Identifier '='] '(' Expression ')' [SelectiveImports] StatementTerminator

SelectiveImports:
	':' SelectiveImport {',' SelectiveImport}

SelectiveImport:
	[Identifier '='] Identifier

When you need access to symbols from other modules, you use an import statement. Import statements can appear any place a statement may appear, and are executed just as other statements. This means you can import modules conditionally. There are two forms of import statements; see Statements for info on that.

Importing modules does not perform a symbolic import as in some other languages. Import statements are more like a declaration of which modules this module depends upon. Since modules are loaded only once into the global namespace, after they have been imported at least once, any code can actually see the module without importing it. However, it's much better to always explicitly import modules which the current module depends upon. This will avoid any problems if you were to change the order of module loading or if you change the names of modules. Always explicitly importing all necessary modules also makes it easy to keep track of dependencies between your modules.

The standard libraries are modules too, meaning you can import them. Sometimes that might be a good idea; for example, if your script needs the io library, it might be a good idea to import it, since some hosts might disable the io library. That way, your script fails with an error saying that it can't find the io library, rather than failing at an arbitrary time later when it tries to access something out of the nonexistent io library.

The Import Mechanism

Module importing is actually handled by the standard library, and can be customized. The modules library page documents the mechanisms involved in loading a module, but the default mechanism works something like this:

  1. The modules.loaded table is checked to see if the module has already been loaded.
    1. If the module has already been loaded, it stops here successfully.
  2. The modules.customLoaders table is checked to see if the module name has a custom loader or namespace associated with it.
    1. If there is a namespace for the module, it stops here successfully.
    2. If there is a loader function, the loader is called.
      1. If the loader function returns a namespace, it stops here successfully.
      2. If the loader returns another function, it calls modules.initModule with that function and the name of the module being imported.
  3. The current namespace hierarchy is examined to ensure that there will be no naming conflicts if the module is loaded. If there would be a conflict, an exception is thrown.
  4. Files are searched for. For each path in the modules.path variable, it looks for either a source (*.md) or compiled module (*.mdm) file. If both are found, it loads the newer one. If just one is found, it loads that.
  5. If the process gets here, nothing has been found, so an exception is thrown.

Scoping and Naming

Importing a module does not bring all its symbols into the current module's namespace. That is, if a module "foo.bar" has a global member "x", and you "import foo.bar", "x" will not be accessible directly. You will have to access it as "foo.bar.x".

There are two bits of syntax that make accessing symbols from other modules a bit easier. First is import renaming, and second is selective imports.

Import renaming is the act of importing a module, and then also declaring a variable which is an alias to that module. For example:

import short = some.really.long.name

This is semantically equivalent to the following:

import some.really.long name
local short = some.really.long.name

Now, you can access members of that long module name either with "some.really.long.name.member" or with "short.member".

Selective imports are similar to import renaming, except they bring in symbols that are inside the imported module.

import some.long.name: one, two, three

This is semantically equivalent to the following:

import some.long.name
local one = some.long.name.one
local two = some.long.name.two
local three = some.long.name.three

That is, one, two, and three are now aliases to some.long.name.one, some.long.name.two, and some.long.name.three, respectively.

Lastly there's one more kind of selective import, a renamed selective import:

import some.long.name: short = longFunctionName

This is equivalent to:

import some.long.name
local short = some.long.name.longFunctionName

Note that in all of these cases, the aliased names are local variables. Sometimes this can't be used. For example, consider the case where a module defines a global variable that contains a value type, like some kind of counter. If you do a selective import, all you'll get is a copy of that counter. For example:

import something: count
writeln(count) // let's say it's 5
something.count++
writeln(count) // still 5
writeln(something.count) // but this is 6
count--
writeln(count) // now 4
writeln(something.count) // still 6

In this case, accessing something.count explicitly is required for proper behavior.

Another potential shortfall of using selective and renamed imports is if you are planning on dynamically reloading modules after they have already been loaded. In this case also, your local aliases will not be updated to point to the newly-reloaded module and its symbols.