Previous chapter: #31: Method Specificity (Change)

Dylan Design Notes

Dylan Design Notes: #32: Module Defining Forms (Addition)

#32: Module Defining Forms (Addition)

Version 1, January 1994 Copyright (c) 1993-1994, Apple Computer
This Design Note specifies a syntax and semantics for module defining forms, which further documents the description of modules found in the Dylan book.

Overview

A module establishes a mapping from variable names to variables (memory locations containing values). It does this in one of two ways: a variable can be owned by the module, or the module may import variables exported by another module by using the other module. Modules export variables to make them accessible to other modules. Only exported variables can be imported by other modules.

Within a given module, a variable name refers to at most one variable. It is an error to create or import two or more different variables with the same name in a single module. If a name does refer to a variable, the variable is said to be accessible from the module. Each variable is owned by exactly one module, but it can be accessible from many modules.

Owned variables are created by the create: keyword argument to define-module and in some cases by definitions associated with a module.

Dylan includes two kinds of definitions. Explicit definitions are created by define-constant, define-variable, define-generic-function, and the class name in define-class. Implicit definitions are created by define-method and the slot specifications of define-class.

Definitions are used both to create variables and to provide values for variables. An explicit definition performed on a variable which was not imported creates an owned variable and provides a value for it. An explicit definition performed on a variable which was imported just provides a value for the variable. An implicit definition has the same behavior, but only if there is no explicit definition for the variable. (If there is an explicit definition for the variable, then the implicit definition does not create the variable, nor does it provide the value for it.)

There must be exactly one explicit definition for each module variable, with the exception that the explicit definition can be left out if there are one or more implicit definitions. Any module variable whose value is a generic function can have any number of implicit definitions.

Programs, module declarations, and expressions

A Dylan program is composed of expressions. Each expression is associated with a module. Within an expression, variables are referenced by variable names. The module associated with the expression provides the mapping from variable name to variable used within the expression. It is an error to reference a variable name for the purpose of getting or setting its value if the variable name does not designate either a variable locally bound with a scope that includes the reference or a variable accessible in the associated module.

A technique for associating an expression with a module is described in the Design Note #33: Headers for Dylan Source Files (Addition).

Module declarations are expressions. Like other defining forms, module declarations are only allowed at top level or inside of begin. The variable names in module declarations are relative either to the module being declared or to a module that it uses, and thus are not affected by the module declaration's associated module. A module declaration can be associated with any module where define-module has its normal meaning.

Before an expression can be compiled, the module declaration for the module associated with the expression must be compiled and then made available to the development environment in an implementation-defined way.

Macros can expand into module declarations. This happens during compilation of the module declaration.

Module declarations

Modules are defined by define-module forms.
define-module module-name { module-clause }*  [Module Declaration]
where each module-clause is either a use clause, a create clause, or an export clause (see below).

This form defines the module with the given name. It describes which modules are used by the module being defined, which variables are imported from the used modules, and which variables are exported by the module being defined.

module-name has the same syntax as identifiers and symbols. Module names are global in scope. Under this proposal, entire programs are compiled as a unit and module names are global to that unit. Though they are not part of this proposal, extensions to allow separate compilation will be considered in the future. The namespace of module names is distinct from that of variables. No variable with the name module-name is created.

Use clauses

There is one or more use-clause for each module used by the module being defined. Each use-clause has the form:
(use used-module-name #key import export prefix exclude rename)

used-module-name is the name of a module to be used. By default all exported variables from the used module are imported by the module being defined, under the same name they had in the used module.

When there are multiple use clauses using the same module, the set of imported variables is the union of those specified by all the use clauses. Some variables may be imported under more than one variable name.

The keyword options are used to prevent some variables from being imported, to give some or all variables different names in the new module, and to re-export variables which were imported from a used module. Each of these keyword options applies within the scope of the particular use clause, and does not affect the behavior of other use clauses (even if the other use clauses indicate the same module).

import: ({variable-name  |  (old-name => new-name) }*) | all

Indicates which variables are to be imported from the module being used. The default is all, meaning that all the variables exported by the used module should be imported. When => appears in an import-option, it specifies both an import and a rename. In other words,

        import: (foo => bar)
is simply an abbreviation for
        import: (foo), rename: (foo => bar)
and means exactly the same thing.
exclude: ({variable-name}*)
Indicates variables which should not be imported. This keyword can only be used if import: is all. The default for the exclude: keyword is ().
prefix: string
Prepends string to the names of variables as they are imported from the used module. This option can be overridden for a particular variable by using the rename: keyword for that variable. The default value is "".
rename: ({(old-name => new-name)}*)
Used to override both the import: keyword and the prefix: keyword. The variables named are imported, regardless of whether the import: keyword indicates they should be imported. old-name indicates the name of the variable in the module being used; new-name indicates the name to be given to the variable in the module being defined. The prefix: keyword of the use clause is ignored for variables specified by the rename: keyword. The default value for the rename: keyword is ().
export: ({variable-name}*) | all
Specifies variables which should be exported from the module being defined. Each of these variables must have been imported by this use clause. variable-name is the name of the variable in the module being defined. It is also the name under which the variable will be exported. It is allowed for the same variable-name to appear more than once, as this is sometimes useful for documentation purposes. all indicates that all the variables imported by this use clause should be exported. The default value for the export: keyword is ().

Exporting owned variables

A module export clause has the following syntax:
(export {variable-name }*)
This option specifies that the named variables are to be exported from the module being defined. Each variable-name is the name of a variable to export. These variables must be defined by a defining form in the module being defined. It is an error if any of the variables were imported from other modules. It is allowed for the same name to appear more than once, since this is sometimes useful for documentation purposes.

A module create clause has the following syntax:

(create {variable-name }*)
This option specifies that the named variables are to be created in and exported from the module being defined. A variable-name is the name of a variable to create and export. These variables must not be defined by a defining form in the module being defined, and they must be defined by a module which uses the module being defined. It is an error if any of the variables were imported from other modules. It is allowed for the same name to appear more than once, since this is sometimes useful for documentation purposes.

Circular uses and imports

Circular use relationships among modules are not allowed. The graph of the module-uses-module relation must be a directed acyclic graph.

Examples

define module graphics
  use dylan;
  create draw-line,
         erase-line,
         invert-line,
         skew-line
         frame-rect,
         fill-rect,
         erase-rect,
         invert-rect;
end module graphics;

define module lines
  use dylan;
  use graphics,
        import: (draw-line,
               erase-line,
               invert-line,
               skew-line);
end module lines;

define module rectangles
  use dylan;
  use graphics,
        prefix: "graphics$",
        exclude: (skew-line);
end module rectangles;

define module dylan-gx
  use dylan, export: all;
  use graphics,
        rename: (skew-line => warp-line),
      export: all;
end module dylan-gx;
The modules created by the above module declarations would have access to variables with the following names:
graphics        draw-line
                erase-line
                invert-line
                skew-line
                frame-rect
                fill-rect
                erase-rect
                invert-rect
                plus all the variables in the Dylan module

lines           draw-line
                erase-line
                invert-line
                skew-line
                plus all the variables in the Dylan module

rectangles      graphics$draw-line
                graphics$erase-line
                graphics$invert-line
                graphics$frame-rect
                graphics$fill-rect
                graphics$erase-rect
                graphics$invert-rect
                plus all the variables in the Dylan module

dylan-gx        draw-line
                erase-line
                invert-line
                warp-line
                frame-rect
                fill-rect
                erase-rect
                invert-rect
                plus all the variables in the Dylan module
The lines and rectangles modules do not export any variables. They are presumably used to provide definitions for the variables created and exported by the graphics modules. The difference between the graphics module and the dylan-gx module is that one variable is renamed, and the dylan-gx module exports the variables which it imports from the dylan module, while the graphics module does not.

Syntax for Module Declarations

define module module-definition

module-definition ::=

        module-name [ module-clauses ] end [ module ] [ module-name ]

module-clauses ::=
        module-clause [ ; ]
        module-clause ; module-clauses

module-clause ::=
        module-use-clause
        module-export-clause
        module-create-clause

module-export-clause ::=
        export variable-names

module-create-clause ::=
        create variable-names

module-use-clause ::=

        use module-name [ , module-use-options ]

module-use-options ::=
        module-use-option
        module-use-option , module-use-options

module-use-option ::=
        prefix-option
        import-option
        exclude-option
        rename-option
        export-option

prefix-option ::=
        prefix: string

import-option ::=
        import: all
        import: import-set

import-set ::=
        ( [ imports ] )

imports ::=
        import
        import , imports

import ::=
        variable-name
        variable-name => variable-name

exclude-option ::=
        exclude: variable-name-set

export-option ::=
        export: all
        export: variable-name-set

rename-option ::=
        rename: ( [ rename-specs ] )

rename-specs ::=
        rename-spec
        rename-spec , rename-specs

rename-spec ::=
        variable-name => variable-name

variable-name-set ::=
        ( [ variable-names ] )

variable-names ::=
        variable-name
        variable-name , variable-names

variable-name ::=
        symbol

module-name ::=
        symbol

Next chapter: #33: Headers for Dylan Source Files (Addition)