The Microsoft code name "M" Modeling Language Specification - Module

November 2009

[This documentation targets the Microsoft SQL Server Modeling CTP (November 2009) and is subject to change in future releases. Blank topics are included as placeholders.]

Sections:
1: Introduction to "M"
2: Lexical Structure
3: Text Pattern Expressions
4: Productions
5: Rules
6: Languages
7: Types
8: Computed and Stored Values
9: Expressions
10: Module
11: Attributes
12: Catalog
13: Standard Library
14: Glossary

10 Module

A module is a scope which contains declarations of types (§2), extents (§8.2), and computed values (§8.1). Modules override lexical scoping to import symbols which have been exported from another module.   

10.1 Compilation Unit

Several modules may be contained within a CompilationUnit, typically a text file.

syntax CompilationUnit

    = ModuleDeclarationList;

syntax ModuleDeclarationList

    = ModuleDeclaration

    | ModuleDeclarationList ModuleDeclaration;

10.2 Module Declaration

A ModuleDeclaration is a named container/scope for type declarations, field declarations, and computed value declarations.

syntax ModuleDeclaration

    = "module" QualifiedIdentifer ModuleBody  ";"?;

syntax QualifiedIdentifier

    = Identifier

    | QualifiedIdentifier "." Identifier;

syntax ModuleBody

    = "{" ImportDirectives? ExportDirectives? ModuleMemberDeclarations?  "}";

syntax ModuleMemberDeclarations

    = ModuleDeclaration

    | ModuleMemberDeclarations ModuleMemberDeclaration;

syntax ModuleMemberDeclaration

    = LanguageDeclaration

    | FieldDeclaration 

    | ComputedValueDeclaration 

    | TypeDeclaration; 

Each ModuleDeclaration has a QualifiedIdentifier that uniquely qualifies the declarations contained by the module.

Each ModuleMemberDeclaration may be referenced either by its Identifier or by its fully qualified name by concatenating the QualifiedIdentifier of the ModuleDeclaration with the Identifier of the ModuleMemberDeclaration (separated by a period).

For example, given the following ModuleDeclaration:

module PeopleData {

    Names : {Text*};

}

The fully qualified name of the field is PeopleData.Names, or using escaped identifiers, 'PeopleData'.'Names'. It is always legal to use a fully qualified name where the name of a declaration is expected.

Modules are not hierarchical or nested. That is, there is no implied relationship between modules whose QualifiedIdentifier share a common prefix.

For example, consider these two declarations:

module A {

    N : Number;

}

module A.B {

    NPlusOne : { N + 1 }

}

Module A.B is in error, as it does not contain a declaration for the identifier N. That is, the members of Module A are not implicitly imported into Module A.B.

10.3 Inter-Module Dependencies

M uses ImportDirectives and ExportDirectives to explicitly control which declarations may be used across module boundaries.

syntax ExportDirectives

    = ExportDirective

    | ExportDirectives ExportDirective;

syntax ExportDirective

    = "export" Identifiers;

syntax ImportDirectives

    = ImportDirective

    | ImportDirectives ImportDirective;

ImportDirective

    = "import"  ImportModules ";" 

    | "import"  QualifiedIdentifier  "{"  ImportMembers  "}"  ";";

syntax ImportMember

    = Identifier  ImportAlias? 

syntax ImportMembers

    = ImportMember 

    | ImportMembers  ","  ImportMember;

syntax ImportModule

    = QualifiedIdentifier  ImportAlias?;

syntax ImportModules

    = ImportModule 

    | ImportModules "," ImportModule;

syntax ImportAlias

    = "as"  Identifier;

A ModuleDeclaration contains zero or more ExportDirectives, each of which makes a ModuleMemberDeclaration available to declarations outside of the current module.

A ModuleDeclaration contains zero or more ImportDirectives, each of which names a ModuleDeclaration whose declarations may be referenced by the current module.

A ModuleMemberDeclaration may only reference declarations in the current module and declarations that have an explicit ImportDirective in the current module.

An ImportDirective is not transitive, that is, importing module A does not import the modules that A imports.

For example, consider this ModuleDeclaration:

module People.Types {

    export Person;

    SecretNumber : Number;

    type Person  { FirstName : Text; Age : Number; }

    People : {Person*};

}

The field People.Types.SecretNumber may only be referenced from within the module People.Types. The type People.Types.Person may be referenced in any module that has an ImportDirective for module People.Types, as shown in this example: 

module People.Data {

    import People.Types;

    export Names;

    Names : {Text*};

    Friends : {People.Types.Person*};

}

The example above used the fully qualified name to refer to People.Types.Person. A qualified name beginning with an imported module name refers to the object in the imported module and is not a new definition. For example, People.Types.People below references the previously declared extent. It does not declare a new extent.

module People.Data {

    import People.Types;

    People.Types.People {

        { Name => "Mary", Age => 23 },

        { Name => "Joe", Age => 32 },

    }

}

An ImportDirective may also specify an ImportAlias that provides a replacement Identifier for the imported declaration:

module People.Data {

    import People.Types as pt;

    export Names;

    Names : {Text*};

    Friends : {pt.Person*};

}

An ImportAlias replaces the name of the imported declaration. That means that the following is an error:

module People.Data {

    import People.Types as pt;

    export Names;

    Names : {Text*};

    Friends : {People.Types.Person*};

}

It is legal for two or more ImportDirectives to import the same declaration, provided they specify distinct aliases. For a given module, at most one ImportDirective may use a given alias.

If an ImportDirective imports a module without specifying an alias, the declarations in the imported module may be referenced without the qualification of the module name. That means the following is also legal:

module People.Data {

    import People.Types;

    export Names;

    Names : {Text*};

    Friends : {Person*};

}

When two modules contain same-named declarations, there is a potential for ambiguity. The potential for ambiguity is not an error – ambiguity errors are detected lazily as part of resolving references.

Consider the following two modules:

module A {

    export X;

    X : Number;

}

module B {

    export X;

    X : Number;

}

It is legal to import both modules either with or without providing an alias:

module C {

    import A, B;

    Y { 1 + 2 }

}

This is legal because ambiguity is only an error for references, not declarations. That means that the following is a compile-time error:

module C {

    import A, B;

    Y { X + 2 } // error: unqualified identifier X is ambiguous

}

This example can be made legal either by fully qualifying the reference to X:

module C {

    import A, B;

    Y { A.X + 2 } // no error

}

or by adding an alias to one or both of the ImportDirectives:

module C {

    import A;

    import B as bb;

    Y { X + 2 } // no error, refers to A.X

    Z { bb.X + 2 } // no error, refers to B.X

}

Because module names may contain periods, there is a potential ambiguity when module names share a common prefix. Consider these two modules:

module A {

    export Z, B;

    type Z { C : Number; }

    B : Z;

}

module A.B {

    export C;

    C : Number;

}

If a module imports both of these modules, the QualifiedIdentifier A.B.C is inherently ambiguous, as it could either refer to the C field in module A.B or to the C field of the B field of module A. To disambiguate, one must use an alias to break the tie:

module F {

    import A;

    import A.B as ab;

    G { ab.C }  // returns the C field of module A.B

    H { A.B.C } // returns the C field of the B field of module A

}

An ImportDirective may either import all exported declarations from a module or only one of them. The latter is enabled by specifying an ImportMember as part of the directive. For example, Module Plot2D imports only Point2D and PointPolar from the module Geometry:

module Geometry {

    export Point2D, Point2DPolar, Point3D;

    type Point2D { X : Number; Y : Number; }

    type Point2DPolar { R : Number; T : Number; }

    type Point3D : Point2D { Z : Number; }

}

module Plot2D {

    import Geometry {Point2D, Point2DPolar};

    Points : {Point2D*};

    PointsPolar : {Point2DPolar*};

}

An ImportDirective that contains an ImportMember only imports the named declarations from that module. This means that the following is a compilation error because module Plot3D references Point3D which is not imported from module Geometry:

module Plot3D {

    import Geometry {Point2D};

    Points : {Point3D*};

}

An ImportDirective that contains an ImportTarget and an ImportAlias assign the replacement name to the imported type, field, or computed value declaration.

10.4 Compilation Episode

Multiple compilation units may contribute declarations to a module of the same name. 

The types and computed values of a module are sealed by a compilation episode. A subsequent compilation episode may not contribute additional types or computed values. Initial values for module level field declarations may be contributed in subsequent compilation episodes.

Each fragment must explicitly import the symbols used within that fragment and may only export symbols defined within that fragment. 

10.5 Storage

All dynamic storage in M is modeled as module-scoped FieldDeclarations called extents. The declaration of an extent may be spread across multiple sections of program text. Consider the following example:

// catalog.m

module Catalog {

    type Product {

        Name : Text;

        Price : Decimal9;

        Product(Name,Price);

    }

    Products : {Product*};

}

// groceries.m

module Catalog {

    Products {

        Product("Soap", 1.29),

        Product("Tuna", 2.49)

    }

}

// hardware.m

module Catalog {

    Products {

        Product("Lightbulb", 0.99),

        Product("Screwdriver", 5.99)

    }

}

The resulting Products extent will contain:

{

    Product("Soap", 1.29),

    Product("Tuna", 2.49),

    Product("Lightbulb", 0.99),

    Product("Screwdriver", 5.99)

}

The mapping of module-scoped FieldDeclarations to physical storage is implementation-specific and outside the scope of this specification.