Collection and List Types

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

Collection and List are two intrinsic types in the Microsoft code name “M” modeling language. A collection is a value that groups together zero or more elements that themselves are values. A typical collection consists of multiple occurrences of the same type, for example, a collection of phone numbers. But it is possible for the elements of a collection to consist of different types of values.

A list is an ordered collection.

The order of elements in a collection is not significant. That means the following two collections are equal.

{ 1, 2 } == { 2, 1 }

Collections can contain duplicate elements. That makes the following expression true.

{ 1, 2, 2 } != { 1, 2 }

Creating Collections

There are two ways to create collections. You can specify the members directly using an initializer or you can define a constructor for the collection.

Initializers

Collections can be defined by using an initializer, { }, where you explicitly list each member of the collection. Use an initializer to create collections whose elements are of different types. The following example uses an initializer to create a collection.

module Coll
{
    Coll : { 1, 2, 3 } ;
}

Collections can be empty, as the following example shows.

module Coll
{
    Coll : { } ;
} 

In the following example, the collection contains a value of type Logical, and one of type Text.

module Coll
{
    Coll : {true, "Hello"  } ;
}  

Note that the elements in a collection can consist of collections or entities, as seen in the following examples.

module Coll
{
    Coll : {{1}, {2} } ;
}
module Coll
{
    Coll : 
        {
            {id => 1, name => "Bob"}, 
            {id => 2, name => "jane"} 
        } ;
}  

Note that a value of a simple type (type General) is not a collection. So the following statement is false.

“Hello” in Collection

Collection Constructors

If a collection contains only one type of element and you do not want to explicitly initialize it, define it using a collection constructor. The constructor specifies the type of element that makes up the collection and the number of elements the collection can contain.

The collection element count is specified using the * operator.

Operator Number of Elements

*

Zero or more.

The most common use of this operator is in creating a collection of entities, which causes a SQL table to be created. In the following example, a Person entity is turned into a People collection by appending the * operator.

module Contacts {
    type Person {
        Name: Text;
        Age: Integer32;
    }
    People : {Person*};
}

Collection Constructors Using Constraints

The collection constructors can use the * operator or can be written as a constraint over the intrinsic type Collection. The following two type declarations describe the same set of collection values.

    type SomeNumbers : {Number*};
    type SomeNumbers1 : Collection where value.Count >= 0 
                                 && value <= Number;

The collection type constructors compose with the where operator, which allows the following type check to succeed.

{ 1, 2 } in (Number where value < 3)* where value.Count % 2 == 0

Note that the inner where operator applies to elements of the collection and the outer where operator applies to the collection itself.

Operators

“M” defines built-in operators that are specific to collections.

Equivalence Operators

As with simple values, the equivalence operators == and != are defined over collections. In “M”, two collections are considered equivalent if, and only if, each element has a distinct equivalent element in the other collection. Thus the following equivalence expressions are all true.

{ 1, 2 } == { 1, 2 }
{ 1, 2} == {2, 1}
{ 1, 2 } != { 1 }
{ 1, 1, 2 } != { 1, 2 }

The in Operator

The in operator tests whether a given value is an element of the collection. The result of the in operator is a logical value that indicates whether the value is or is not an element of the collection. For example, these expressions are both true.

1 in { 1, 2, 3 }
!(1 in { "Hello", 9 })

Note that the in operator is not the same as the subset operator. So the following expression is true.

{ 1, 2 } in {  { 10, 20 } , { 1, 2 } }

But the following expression generates a type incompatibility compiler error, because the set {1, 2} is the wrong type to be a member of the set {1, 2, 3}.

!({ 1, 2 } in { 1, 2, 3 })

The Subset and Superset Operators

“M” defines the subset and superset operators using the <= and >= symbols respectively. The following expressions are all true.

{ 1, 2 } <= { 1, 2, 3 }
{ "Hello", "World" } >= { "World" }
{ 1, 2, 1 } <= { 1, 2, 3 }

The # Operator

The postfix # operator returns the number of elements in a collection, so the following two expressions are true.

{ 1, 2, 2, 3 }# == { 1, 2, 2, 3 }.Count

Set Operators

“M” also defines set union "|" and set intersection "&" operators, which also yield sets.

({ 1, 2, 3, 1 } | { 1, 2, 4 }) == { 1, 2, 3, 4 }
({ 1, 2, 3, 1 } & { 1, 2, 4 }) == { 1, 2 }

Note that union and intersection always return collections that are sets, even when applied to collections that contain duplicates.

Where

The most common collection operator is the where operator, which applies a logical expression (called the predicate) to each element in a collection (called the domain) and results in a new collection that consists of only the elements for which the predicate holds true.

To reference the element in the predicate, the identifier value represents each specific element being tested. For example, consider this expression that uses a where operator.

{ 1, 2, 3, 4, 5, 6 } where value > 3

In this example, the domain is the collection { 1, 2, 3, 4, 5, 6 } and the predicate is the expression value > 3. Note that the identifier value is available only within the scope of the predicate expression. The result of this expression is the collection { 4, 5, 6 }.

Queries

“M” supports a rich set of query expressions using syntax similar to that of LINQ. For example, the preceding where example can be written in long form as follows.

from n in { 1, 2, 3, 4, 5, 6 } where n > 3 select n

In general, “M” supports the LINQ operators with these significant exceptions:

  • ElementAt, First, Last, Range, and Skip are not supported: “M” collections are unordered and do not support positional access to elements.

  • Reverse is not supported: again, position is not significant in “M” collections.

  • Take, TakeWhile, and Single: These operators do not exist in “M”.

  • ToArray, ToDictionary, and ToList: There are no corresponding CLR types in “M”.

  • Cast: Typing works differently in “M”. You can achieve the same effect using a where operator. You can also cast in-place using the “:” operator.

While the where operator allows elements to be accessed based on a calculation over the values of each element, there are situations where it would be much more convenient to assign names to each element and then access the element values by its assigned name. “M” defines a distinct kind of value, called an entity. for just this purpose.

Members

“M” defines a number of members on all collections.

Distinct

“M” collections can contain duplicates. The Distinct member returns a version of a collection with any duplicates removed. The result of applying Distinct to a collection is not just another collection, but it is also a set. The following is an example.

{ 1, 2, 3, 1 }.Distinct == { 1, 2, 3 }

Count

Using the Count member on collections calculates the number of elements in a collection. In the following example, the use of that operator results in the value 4.

{ 1, 2, 2, 3 }.Count

Choose

The Choose member returns an arbitrary, but not random, element of a collection.

Logical Collections

The following members are defined on collections of type Logical*.

All() : Logical;

Exists() : Logical;

All returns true if every member of a collection is true. Exists returns true if at least one member of a collection is true.

Number Collections

The following members are defined on collections of type Number*.

Average() : Scientific;

Maximum() : Number;

Minimum() : Number;

Sum() : Number;

Maximum, Minimum, and Sum are specialized to return the element type of the collection.

Accessing Entity Collection Elements with Indexers

In languages like VB and C#, you can access collection elements in different ways depending on the collection type. For example, you can access arrays using a numeric index, which specifies the position of the element to access.

But an enitity collection is not like an array and you cannot access an element by its position within the collection, because collection elements do not have an intrinsic position. For example, the following statement is true because the two collections are the same.

{ 1, 2} == {2, 1}

For non-entity collections, “accessing” an element is equivalent to checking whether an element is in the collection by using the in keyword. But for entity collections, you may want to access the person whose last name is “Smith” or you may want to see the names of everyone whose account is past due. The “M” compiler automatically generates indexers for all fields of the entities in a collection, which can be used to access members. Entity collections have two kinds of indexers: selectors and projectors.

Selectors

A selector returns from a collection those entities with a field that matches a specified value, as in the following entity collection.

type Person 
{
    Id  : Integer32;
    Name : Text;
    HairColor : Text;
} where identity Id;
People : Person* 
{
    {Id => 1, Name => "Mary", HairColor => "Brown"},
    {Id => 2, Name => "John", HairColor => "Brown"},
    {Id => 3, Name => "Fritz", HairColor => "Blue"}
};

The following expression returns the entity with Name = Mary.

People.Name("Mary") 

The general form of selector expressions is Collection.MemberField(Expression).

The next expression returns those entities with HairColor = Brown.

People.HairColor("Brown")

If there are no entities in the collection that safisfy the selector expression, then the empty collection is returned, as shown in the following code. People.Name("Bill").

To select on the identity field, there is a special form, where the identity indexer is an indexer directly on the collection, as seen in the following expression, which returns the entity with Id = 3: People(3)

The preceding expression is a short-hand version of the earlier selection expression People.Id(3).

If a collection has an identity that consists of multiple fields, they are the default indexer as shown in the following example.

module calcs 
{
    type Person 
    {
        Name : Text(32);
        HairColor : Text(16);
    } where identity (Name, HairColor);
    People : {Person*};
}

In this example, you could select an entitiy with the following expression.

People("Mary", "Brown")

Selector expressions are a shorthand equivalent of queries, so you can also write the following code, which returns the entity with Name = Mary.

from p in People
where p.Name == "Mary"
select p

Projectors

Projectors return all the values of a specific field in the entities of a collection, as shown in the following collection.

module a
{
    type Person 
    {
        Id  : Integer32;
        Name : Text;
        HairColor : Text;
    } where identity Id;
    People : {Person*} 
    {
        {Id => 1, Name => "Mary", HairColor => "Brown"},
        {Id => 2, Name => "John", HairColor => "Brown"},
        {Id => 3, Name => "Fritz", HairColor => "Blue"}  
    }; 
}

For example, you can return all the first names of the People collection with the following code.

People.Name

You can return all the hair colors with the following code.

People.HairColor

Note that the returned collection may contain duplicates. To obtain a duplicate-free collection, use Distinct.

People.HairColor.Distinct == {"Brown", "Blue"}

As is the case with selectors, projector expressions are equivalent to queries, as shown in the following expression.

Collection.MemberField

It is equivalent to the following query.

from c in Collection
select c.MemberField

If the identifier field used for the projector expression is the same as the name of a member of the collection, that field cannot be projected. Specifically, Choose, Count, and Distinct perform the actions listed in this help topic even though there are fields named Choose, Count or Distinct in the entity type in the collection.