"M" Types Overview

[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.]

Types are one of the fundamental concepts of the Microsoft code name “M” modeling language. Types are used to categorize values.

Types can be defined in two different ways:

  1. By specifying the logical predicates that its values must satisfy. For example, the type of even integers is those integers divisible by 2.

  2. By specifying the members of a type. An enumeration is an example.

Any type can be described in either way, but often one way is much easier. For example, it is a bit difficult to list the entire set of even integers. And conversely, it is usually much easier to explictly list the members of an enumeration than to write the logical predicate that defines it.

A type is an expression that describes a set of values. When it is said that a value is of a type, it means that it is in the set of values described by the type.

The “M” modeling language distinguishes how values are described (types) from the actual storage of the values (extents). Stating that a value is of a particular type does not cause that value to be stored anywhere. To store values, you create an extent, which when compiled causes a T-SQL table to be created and may cause values to be inserted into the table. You can create extents directly, without using types. You can also specify that an extent represents a particular type. For more information, see Values in "M".

The “M” types are divided into two categories: intrinsic and derived. An intrinsic type is a type that is already defined in the “M” modeling language and understood by the “M” compiler. A derived type is one that you define by writing “M” code.

For a fuller description of the concepts of the “M” modeling language, see Basic Concepts in "M".

Object Orientation and Types

In “M” a value might conform to any number of types. This is different from the way many object-based languages work, in which a value is created as an instantiation of a class, is a member of that class, and is also a member of all that class’s ancestors. But in “M”, a value can conform to any number of types that are not necessarily in a hierarchical relationship. So “M” is not an object-oriented language.

In the following example, two types are defined and show various cases of type membership.

type A : Number where value < 100;
type B : Number where value > -3;

Given these two type definitions, all of the following expressions evaluate to true.

1 in A
1 in B
-4 in A  // but not in B
102 in B // but not in A

Specifying the Type

You can specify the type of a value with the ascription operator (:). The following example uses this operator.

Amount : Integer32

If you do not specify the type of a value, “M” infers a type for it. The “M” language can always ascribe some type to a value, because even without any context, the value conforms to the root of the intrinsic type hierarchy, Any.

When a value occurs in an expression, “M” can infer the expected type of that value based on the declared result type for the operator or function being applied. For example, the result of the logical and operator (&&) can be inferred to be of type Logical.

Verifying Type Membership

To test whether a value conforms to a type, use the in keyword. This keyword determines whether the value is in the collection of values specified by the type and returns a Boolean value accordingly. In the following code sample, the expressions all return true.

1 in Number
"Hello, world" in Text

Intrinsic Types

The “M” language defines a number of intrinsic types as part of the language. Intrinsic types can be concrete or abstract.

Concrete types such as Text or Integer32 are similar to data types in other languages. The “M” compiler maps the concrete type to specific types in T-SQL, such as varchar or int.

Abstract types allow a business analyst to initially define a field in general terms. Later an architect or developer can specify it more concretely. Abstract types may have a number of abstract and concrete subtypes. For example, Number includes Integer and Decimal, and Integer includes Integer32, which is a concrete type.

There are also some more inclusive abstract types, such as Any or General. These types are somewhat similar to the Object class used in .NET Framework languages.

Most intrinsic types are simple or atomic, in that they get applied to a single field or value. These types all have the General type as an ancestor.

There are three intrinsic types that are compound or composite, instead of being defined by a single field:

  • Collection: Multiple occurrences of values, which have no intrinsic ordering and there can be any number of values in the collection.

  • List: An ordered collection.

  • Entity: A limited number of named fields that are accessed directly by name.

Note that you can have collections of entities, which are used extensively when defining extents.

The following table lists some common intrinsic types. Those types described as abstract may have additional subtypes. For example, Integer32 is a subtype of Integer. For a complete list of all types, see "M" Types Overview ("M" Reference)

Type Super Type Abstract? Description

Any

No

All values.

General

Any

No

All values except for collections and entities.

Number

General

Yes

Any numeric value.

Decimal

Number

Yes

A fixed-point or exact number (subtypes Number).

Integer

Decimal

Yes

A signed, integral value (subtypes Decimal).

Integer32

Integer

Yes

A signed, integral value (subtypes Decimal).

Scientific

Number

Yes

A floating-point or exact number (subtypes Number).

Date

General

No

A calendar date.

DateTime

General

No

A calendar date and time of day.

Time

General

No

A time of day and time zone.

Text

General

No

A sequence of Characters.

Character

General

No

A single Unicode character of text.

Logical

General

No

A logical flag.

Guid

General

No

A globally unique identifier.

Collection

Any

No

An unordered group of (potentially duplicate) values.

List

Any

No

An ordered collection.

Entity

Any

No

A collection of labeled values, either fields or computed values.

Null

Any

No

Contains the single value null.

For each intrinsic type, “M” defines a set of operators that are specific to that type.

Note that the names of the built-in types are available directly in the “M” language.

Derived Types

Derived types can be created from existing types. There are several ways to do this:

  • By using type declarations.

  • By specifying types implicitly using extent declarations.

Explicit Type Creation

Derived types can be explicitly created by using the type keyword.

A major use of the type declaration is to enable reuse of common definitions, which can save you from having to repeatedly define fields that exist in different places. For example, in defining an eCommerce database, you might find yourself defining identical address fields used for Customers or Suppliers. You could instead define an Address type and reuse that type when defining a Customer or Supplier, and thus avoid duplication.

There are a number of ways you can derive a new type from an existing type. Two of the most useful methods are:

  • Create a collection of entities.

  • Specify explicit members.

Collections of Entities

Collections of entities are used to define extents and therefore SQL tables. You create an entity that describes the table layout and then you create a collection of that entity, which causes an extent (and therefore a table) to be created.

The following is an example of this. First a person entity is created, composed of multiple fields of different types. Then a people collection of multiple person entities is defined. The people collection causes an SQL people table to be created.

module contacts 
{
    type person 
    {
        id : Integer32;
        name: Text;
        address: Text;
    } where identity id ;
    people: person*;
}

For more information about how to create foreign key relationships between extents, see One-to-Many Relationships.

Enumerations

Another common use is to create enumerations. You create a type by specifying explicitly the values that make it up. The following is an example. A type is defined that includes different color names. You can then test a value for membership in the Colors type by using the in operator.

module ColorDef
{
    type Colors {"red", "yellow", "blue"};
}

More Ways to Create New Types

The following is a list of additional ways you can create new types using the type declaration.

  • Adding constraints to a type.

  • Specify a collection and initializing its contents. The preceding discussion of enumerations is an example of this.

  • Turn a type into a collection.

  • Create synonyms or aliases for an existing type.

Type declarations can be composed. This means that you can create a new type by applying one of the preceding operations to an existing type and then use that new type as the basis for creating a third type by applying one of the preceding operations again. The earlier discussion on creating collections of entities did exactly this.

The following is an additional example of composing types.

type TinyText : Text where value.Count < 6;
type TTs: {TinyText*};

The preceding code example creates a new type with the name of TinyText, which conforms to the Text type with the additional constraint that the length of the text value is less than 6 characters. It then creates a second TTs type, as a collection of 0 or more TinyText types by appending one of the Kleene operators to the type name.

Constraints on Types

You can apply a predicate to a type by using the where keyword.

type SmallText : Text where value.Count < 7;

In this example, you have constrained the possible Text values to those in which the value contains less than seven characters. That means that the following declarations are true.

"Terse" in SmallText
!("Verbose" in SmallText)

For more information, see Constraints on Types.

Collections

With a few exceptions, “M” allows types to be used as collections. You can turn most types into collections by appending an “*” to the type definition, which specifies 0 to many occurrences. The example discussed earlier, where an entity was turned into a collection, is a common example. For more information, see Collection and List Types

Entities

You can combine multiple types together into an entity. As you saw earlier, this entity can be used to create a collection.

For more information, see Entity Types.

Type Synonyms

You can create alternative names for an already defined type. You might want to do this because a business domain has an already established terminology that you want to conform to.

The following type declaration introduces the type name MyText as a synonym for the Text simple type.

type MyText : Text;

Note that this type and the type Text have the same collection of values that satisfy them, so they describe the same type, but with different names.

Implicit Type Creation

Types can be created implicitly, without using the type keyword. The following example creates a People extent without referencing a Person type.

module M 
{
    people : 
       {{
            Id : Integer32;
            name : Text;
            address : Text;
        }*} where identity (Id);
}

Note that the implicit type in the preceding example is the same type as that defined in the following explicit example. A type is a collection of values and if two types define the same collection, then they are the same types.

Another way to create a type implicitly is to create an entity collection by specifying multiple named fields, which can be of different types. This effectively allows you to nest types implicitly. The following code is an example.

module ExtentImplititTypeValues
{
    People : {{
                Id : Integer32; 
                name : Text; 
                address : Text;
             }*} where identity(Id);
    People  
    {
        { Id => 100, name => "Bob"     , address => "seattle" },
        { Id => 200, name => "Jane"    , address => "Tacoma"},
        { Id => 300, name => "Hillary" , address => "Portland"},
        { Id => 400, name => "Joe"     , address => "sf"},
    };
}

These examples highlight once again that type in “M” is structural: if two types specify the same set of possible values, then they are identical.