Compartir a través de


Program Structure: Modules ("M" Programming Guide)

[This content is no longer valid. For the latest information on "M", "Quadrant", SQL Server Modeling Services, and the Repository, see the Model Citizen blog.]

The module is the basic container for Microsoft code name “M” programs. All type declarations must appear inside a module definition. Extent and computed value declarations can appear inside a module definition or can appear inside a type declaration. A module scope is mandatory. Unlike C#, which allows class declarations outside of a namespace, all types in “M” must be declared within a module.

A module defines a top-level namespace for any type names that are defined. A module also defines a scope for defining extents that store actual values, as well as computed values.

Module definitions cannot be nested: a module statement cannot appear inside another module. Module definitions can be done partially: pieces of module definitions with a given module name can appear in multiple files.

A module name can be any valid identifier, which can include the character “.”. When the “.” character is included in the module name, there is no implication of hierarchy, unlike the case with languages like C#.

The module controls visibility of the declarations within it: accessibility is controlled by using import and export statements.

Basic Code Sample

The following is a basic module definition.

module Contacts 
{
    type Person 
    {
        Name: Text;
    }

    People : {Person*} 
    {
        {Name => "Keith Harris"},
        {Name => "Yun-Feng Pen"},
        {Name => "Simon Pearson"},
        {Name => "Howard Gonzalez"}
    }
}

In this example, the module defines one type named Contacts.Person and one extent named Contacts.People. The type declaration Person describes what person values looks like, but does not mention any locations where those values can be stored. The extent declaration People defines actual storage, and initializes it with values.

Storage

The module in the preceding example includes the extent declaration People that is declared outside of a type name (module-scoped). Module-scoped extent declarations are identical in syntax to those in entity types. However, the storage for an extent declared in an entity type declaration is determined when the type is applied. In contrast, extents declared at module-scope name actual storage that must be mapped by an implementation to load and interpret the module.

Referencing Declarations in other Modules

Use the following keywords for referencing declarations in other modules:

  • export

  • import

  • as

Export

To enable a declaration in a module to be referenced by other modules, use an export statement, as shown in the following module.

module Contacts 
{
    export Person;
    export People;
    type Person 
   {
        Name: Text;
    }
    type Address 
   {
        Street: Text;
    }
    People : {Person*}  
   {
        {Name => "Elena Nicolello"},
        {Name => "David Wright"},
        {Name => "Jon Grande"},
        {Name => "Jerry Orman"}
    }
}

In this example, the type Person and the extent People can be referenced from within another module. Address is not visible outside its module.

Import

Modules can refer to declarations in other modules by using an import directive to name the module that contains the referenced declarations.

The following definition of MyContacts shows how to do this. It references the Contacts module in the preceding example.

module MyContacts 
{
  import Contacts; // declares Person and People
  type BusinessContact : Person;
}

The import statement specifies the name of the module from which to import. It can optionally list the specific declarations to be imported, as shown in the following example.

module MyContacts 
{
  import Contacts {Person}; // declares Person 
  type BusinessContact : Person;
}

Note that in the preceding example, only the named members are imported, so a reference to People generates an error.

You can specify the members to be imported by listing their name. But in more complex modules you may be importing from many other modules, and it is often useful to specify the fully qualified name by including the module name. The following example shows this.

module Contacts 
{
    export Person;
    type Person 
   {
        Name: Text;
    }
}
module MyContacts 
{
  import Contacts; // declares Person 
  type BusinessContact : Contacts.Person;
}

If there are identical names inside the module, and the module that it is importing from, the name in the module doing the importing overrides the name in the module that is exporting the name. If instead you want to use the member from the exporting module, use a fully-qualified name that references the exporting module.

Within a module, any import directives must appear before any export directives, and any import or export directives must appear before any declarations.

Modules can have circular dependencies.

Transitivity

Imports are not transitive, that is, importing module A does not import the modules that A imports.

For example, consider this module declaration.

module Contacts 
{
    export Person;
    type Person 
   {
        Name: Text;
    }
}
module MyContacts 
{
  import Contacts; // declares Person 

  type BusinessContact : Person;
}

The following code generates an error, because while the Person type is exported by the Contacts module, the Contacts module is not directly imported by the People.Data module: the Person type is not imported transitively.

module People.Data 
{
    import MyContacts;
    MyBusinessContact : Contacts.Person;
}

Using Aliases

The previous examples used the fully-qualified name to refer to Contacts.Person. An import statement can also specify an import alias that provides a replacement identifier for the imported declaration.

module People.Data 
{
    import Contacts as con;
    export Names;
    Names : {Text*};
    Friends : {con.Person*};
}

An import alias replaces the name of the imported declaration. That means that the following is an error.

module People.Data 
{
    import Contacts as con;
    export Names;
    Names : {Text*};
    Friends : {Contacts.Person*};
}

Two or more import statements can import the same declaration, provided they specify different aliases. For a given compilation episode, only one import statement can use a given alias.

Note that you can also provide aliases at the member level, as shown in the following code.

module MyContacts 
{
  import Contacts {Person as p}; // declares p
  type BusinessContact : p;
}