Home > Articles > Programming

This chapter is from the book

1.2 Types

Expressions give us a great way to write down how to calculate values based on other values. Often, we want to write down how to categorize values for the purposes of validation or allocation. In M, we categorize values using types.

An M type describes a collection of acceptable or conformant values. We use types to constrain which values may appear in a particular context (for example, an operand, a storage location).

With a few notable exceptions, M allows types to be used as collections. For example, we can use the in operator to test whether a value conforms to a given type. The following expressions are true:

1 in Number
"Hello, world" in Text

Note that the names of the built-in types are available directly in the M language. We can introduce new names for types using type declarations. For example, this type declaration introduces the type name My Text as a synonym for the Text simple type:

type [My Text] : Text;

With this type name now available, we can write the following:

"Hello, world" in [My Text]

Note that the name of the type [My Text] contains spaces and is subject to the same escaping rules as the member names in entities.

While it is moderately useful to introduce your own names for an existing type, it’s far more useful to apply a predicate to the underlying type:

type SmallText : Text where value.Count < 7;

In this example, we’ve constrained the universe of possible Text values to those in which the value contains less than seven characters. That means that the following holds true:

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

Type declarations compose:

type TinyText : SmallText where value.Count < 6;

The preceding is equivalent to the following:

type TinyText : Text where value.Count < 6;

It’s important to note that the name of the type exists so an M declaration or expression can refer to it. We can assign any number of names to the same type (for example, Text where value.Count < 7) and a given value either conforms to all of them or to none of them. For example, consider this example:

type A : Number where value < 100;
type B : Number where value < 100;

Given these two type definitions, both of the following expressions will evaluate to true:

1 in A
1 in B

If we introduce the following third type:

type C : Number where value > 0;

we can also state this:

1 in C

In M, types are sets of values, and it is possible to define a new type by explicitly enumerating those values:

type PrimaryColors { "Red", "Blue", "Yellow" }

This is how an enumeration is defined in M. Any type in M is a collection of values. For example, the types Logical and Integer8 defined next could be defined as the collections:

{ true, false }
{-128, -127, ..., -1, 0, 1, ..., 127}

A general principle of M is that a given value may conform to any number of types. This is a departure from the way many object-based systems work, in which a value is bound to a specific type at initialization-time and is a member of the finite set of subtypes that were specified when the type was defined.

One last type-related operation bears discussion—the type ascription operator “:”. The type ascription operator asserts that a given value conforms to a specific type.

In general, when we see values in expressions, M has some notion of 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 “&&” is declared to be conformant with type Logical.

It is occasionally useful (or even required) to apply additional constraints to a given value—typically to use that value in another context that has differing requirements.

For example, consider the following simple type definition:

type SuperPositive : Number where value > 5;

And let’s now assume that there’s a function named CalcIt that is declared to accept a value of type SuperPositive as an operand. We’d like M to allow expressions like this:

CalcIt(20)
CalcIt(42 + 99)

and prohibit expressions like this:

CalcIt(-1)
CalcIt(4)

In fact, M does exactly what we want for these four examples. This is because these expressions express their operands in terms of simple built-in operators over constants. All of the information needed to determine the validity of the expressions is readily and cheaply available the moment the M source text for the expression is encountered.

However, if the expression draws upon dynamic sources of data or user-defined functions, we must use the type ascription operator to assert that a value will conform to a given type.

To understand how the type ascription operator works with values, let’s assume that there is a second function, GetVowelCount, that is declared to accept an operand of type Text and return a value of type Number that indicates the number of vowels in the operand.

Since we can’t know based on the declaration of GetVowelCount whether its results will be greater than five or not, the following expression is not a legal M expression:

CalcIt( GetVowelCount(someTextVariable) )

Because GetVowelCount’s declared result type Number includes values that do not conform to the declared operand type of CalcIt, which is SuperPositive, M assumes that this expression was written in error and will refuse to even attempt to evaluate the expression.

When we rewrite this expression to the following legal expression using the type ascription operator:

CalcIt( GetVowelCount(someTextVariable) : SuperPositive )

we are telling M that we have enough understanding of the GetVowelCount function to know that we’ll always get a value that conforms to the type SuperPositive. In short, we’re telling M we know what we’re doing.

But what if we don’t? What if we misjudged how the GetVowelCount function works and a particular evaluation results in a negative number? Because the CalcIt function was declared to accept only values that conform to SuperPositive, the system will ensure that all values passed to it are greater than five. To ensure this constraint is never violated, the system may need to inject a dynamic constraint test that has a potential to fail when evaluated. This failure will not occur when the M source text is first processed (as was the case with CalcIt(-1))—rather it will occur when the expression is actually evaluated.

Here’s the general principle at play.

M implementations will typically attempt to report any constraint violations before the first expression is evaluated. This is called static enforcement, and implementations will manifest this much like a syntax error. However, as we’ve seen, some constraints can only be enforced against live data and, therefore, require dynamic enforcement.

In general, the M philosophy is to make it easy for users to write down their intention and put the burden on the M implementation to “make it work.” However, to allow a particular M program to be used in diverse environments, a fully featured M implementation should be configurable to reject M program that rely on dynamic enforcement for correctness to reduce the performance and operational costs of dynamic constraint violations.

1.2.1 Collection Types

M defines a type constructor for specifying collection types. The collection type constructor restricts the type and count of elements a collection may contain. All collection types are restrictions over the intrinsic type Collection, which all collection values conform to:

{ } in Collection
{ 1, false } in Collection
! ("Hello" in Collection)

The last example is interesting, in that it illustrates that the collection types do not overlap with the simple types. There is no value that conforms to both a collection type and a simple type.

A collection type constructor specifies both the type of element and the acceptable element count. The element count is typically specified using one of the three operators:

T* - zero or more Ts
T+ - one or more Ts
T#m..n - between m and n Ts

The collection type constructors can either use operators or be written longhand as a constraint over the intrinsic type Collection:

type SomeNumbers : Number+;
type TwoToFourNumbers : Number#2..4;
type ThreeNumbers : Number#3;
type FourOrMoreNumbers : Number#4..;

These types describe the same sets of values as these longhand definitions:

type SomeNumbers : Collection where value.Count >= 1
                                    && item in Number;
type TwoToFourNumbers : Collection where value.Count >= 2
                                    && value.Count <= 4
                                    && item in Number;
type ThreeNumbers : Collection where value.Count == 3
                                    && item in Number;
type FourOrMoreNumbers : Collection where value.Count >= 4
                                    && item in Number;

In the case that value itself is a collection, an additional variable item is introduced into scope. The item variable ranges over the elements of value (which must be a collection). Clauses that use item must hold for every element of value.

Independent of which form is used to declare the types, we can now assert the following hold:

!({ } in TwoToFourNumbers)
!({ "One", "Two", "Three" } in TwoToFourNumbers)
{ 1, 2, 3 } in TwoToFourNumbers
{ 1, 2, 3 } in ThreeNumbers
{ 1, 2, 3, 4, 5 } in FourOrMoreNumbers

The collection type constructors compose with the where operator, allowing the following type check to succeed:

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

Note that the where inside the parentheses applies to elements of the collection, and the where outside the parentheses operator applies to the collection itself.

1.2.2 Nullable Types

We have seen many useful values: 42, "Hello", {1,2,3}. The distinguished value null serves as a placeholder for some other value that is not known. A type with null in the value space is called a nullable type. The value null can be added to the value space of a type with an explicit union of the type and a collection containing null or using the postfix operator ?. The following expressions are true:

! (null in Integer)
null in Integer?
null in (Integer | { null } )

The ?? operator converts between a null value and known value:

null ?? 1 == 1
3 ?? 1 == 3

Arithmetic operations on a null operand return null:

1 + null == null
null * 3 == null

Logical operators, conditional, and constraints require non-nullable operands.

1.2.3 Entity Types

Just as we can use the collection type constructors to specify what kinds of collections are valid in a given context, we can do the same for entities using entity types.

An entity type declares the expected members for a set of entity values. The members of an entity type can be declared either as fields or as computed values. The value of a field is stored; a computed value is evaluated. All entity types are restrictions over the Entity type.

Here is the simplest entity type:

type MyEntity : Language.Entity;

The type MyEntity does not declare any fields. In M, entity types are open in that entity values that conform to the type may contain fields whose names are not declared in the type. That means that the following type test:

{ X = 100, Y = 200 } in MyEntity

will evaluate to true, as the MyEntity type says nothing about fields named X and Y.

Most entity types contain one or more field declarations. At a minimum, a field declaration states the name of the expected field:

type Point { X; Y; }

This type definition describes the set of entities that contain at least fields named X and Y irrespective of the values of those fields. That means that the following type tests will all evaluate to true:

{ X = 100, Y = 200 } in Point

// more fields than expected OK
{ X = 100, Y = 200, Z = 300 } in Point

// not enough fields – not OK
! ({ X = 100 } in Point)

{ X = true, Y = "Hello, world" } in Point

The last example demonstrates that the Point type does not constrain the values of the X and Y fields—any value is allowed. We can write a new type that constrains the values of X and Y to numeric values:

type NumericPoint {
  X : Number;
  Y : Number where value > 0;
}

Note that we’re using type ascription syntax to assert that the value of the X and Y fields must conform to the type Number. With this in place, the following expressions all evaluate to true:

{ X = 100, Y = 200 } in NumericPoint
{ X = 100, Y = 200, Z = 300 } in NumericPoint
! ({ X = true, Y = "Hello, world" } in NumericPoint)
! ({ X = 0, Y = 0 } in NumericPoint)

As we saw in the discussion of simple types, the name of the type exists only so that M declarations and expressions can refer to it. That is why both of the following type tests succeed:

{ X = 100, Y = 200 } in NumericPoint
{ X = 100, Y = 200 } in Point

even though the definitions of NumericPoint and Point are independent.

1.2.4 Declaring Fields

Fields are named units of storage that hold values. M allows you to initialize the value of a field as part of an entity initializer. However, M does not specify any mechanism for changing the value of a field once it is initialized. In M, we assume that any changes to field values happen outside the scope of M.

A field declaration can indicate that there is a default value for the field. Field declarations that have a default value do not require conformant entities to have a corresponding field specified. (We sometimes call such field declarations optional fields.) For example, consider this type definition:

type Point3d {
  X : Number;
  Y : Number;
  Z = -1 : Number; // default value of negative one
}

Because the Z field has a default value, the following type test will succeed:

{ X = 100, Y = 200 } in Point3d

Moreover, if we apply a type ascription operator to the value:

({ X = 100, Y = 200 } : Point3d)

we can now access the Z field like this:

({ X = 100, Y = 200 } : Point3d).Z

This expression will yield the value -1.

If a field declaration does not have a corresponding default value, conformant entities must specify a value for that field. Default values are typically written down using the explicit syntax shown for the Z field of Point3d. If the type of a field is either nullable or a zero-to-many collection, then there is an implicit default value for the declaring field of null for optional and {} for the collection.

For example, consider this type:

type PointND {
  X : Number;
  Y : Number;
  Z : Number?;        // Z is optional
  BeyondZ : Number*;  // BeyondZ is optional too
}

Again, the following type test will succeed:

{ X = 100, Y = 200 } in PointND

and ascribing the PointND to the value will allow us to get these defaults:

({ X = 100, Y = 200 } : PointND).Z == null
({ X = 100, Y = 200 } : PointND).BeyondZ == { }

The choice of using a nullable type versus an explicit default value to model optional fields typically comes down to style.

1.2.5 Declaring Computed Values

Calculated values are named expressions whose values are computed rather than stored. Here’s an example of a type that declares a computed value, IsHigh:

type PointPlus {
  X : Number;

  Y : Number;

// a computed value

  IsHigh() : Logical { Y > 0 }

}

Note that unlike field declarations which end in a semicolon, computed value declarations end with the expression surrounded by braces.

Like field declarations, a computed value declaration may omit the type ascription, as this example does:

type PointPlus {
  X : Number;
  Y : Number;
// a computed value with no type ascription
  InMagicQuadrant() { IsHigh && X > 0 }
  IsHigh() : Logical { Y > 0 }
}

When no type is explicitly ascribed to a computed value, M will infer the type automatically based on the declared result type of the underlying expression. In this example, because the logical-and operator used in the expression was declared as returning a Logical, the InMagicQuadrant computed value also is ascribed to yield a Logical value.

The two computed values we just defined and used didn’t require any additional information to calculate their results other than the entity value itself. A computed value may optionally declare a list of named parameters whose actual values must be specified when using the computed value in an expression. Here’s an example of a computed value that requires parameters:

type PointPlus {
  X : Number;
  Y : Number;
  // a computed value that requires a parameter
  WithinBounds(radius : Number) : Logical {
    X * X + Y * Y <= radius * radius
  }
  InMagicQuadrant() { IsHigh && X > 0 }
  IsHigh() : Logical { Y > 0 }
}

To use this computed value in an expression, you must provide values for the parameters:

({ X = 100, Y = 200 } : PointPlus).WithinBounds(50)

When calculating the value of WithinBounds, M will bind the value 50 to the symbol radius—this will cause the WithinBounds computed value to evaluate to false.

It is useful to note that both computed values and default values for fields are part of the type definition, not part of the values that conform to the type. For example, consider these three type definitions:

type Point {
  X : Number;
  Y : Number;
}
type RichPoint {
  X : Number;
  Y : Number;
  Z = -1 : Number;
  IsHigh() : Logical { X < Y }
}
type WeirdPoint {
  X : Number;
  Y : Number;
  Z = 42 : Number;
  IsHigh() : Logical { false }
}

Because RichPoint and WeirdPoint have only two required fields (X and Y), we can state the following:

{ X=1, Y=2 } in RichPoint
{ X=1, Y=2 } in WeirdPoint

However, the IsHigh computed value is only available when we ascribe one of these two types to the entity value:

({ X=1, Y=2 } : RichPoint).IsHigh == true
({ X=1, Y=2 } : WeirdPoint).IsHigh == false

Because IsHigh is purely part of the type and not the value, when we chain the ascription like this:

(({ X=1, Y=2 } : RichPoint) : WeirdPoint).IsHigh == false

the outer-most ascription that determines which function is called.

A similar principle is at play with respect to how default values work. Again, the default value is part of the type, not the entity value. When we write the following expression:

({ X=1, Y=2 } : RichPoint).Z == -1

the underlying entity value still only contains two field values (1 and 2 for X and Y respectively). Where default values differ from computed values is when we chain ascriptions. Consider this expression:

(({ X=1, Y=2 } : RichPoint) : WeirdPoint).Z == -1

Because the RichPoint ascription is applied first, the resultant entity has a field named Z whose value is -1; however, there is no storage allocated for the value. (It’s part of the type’s interpretation of the value.) When we apply the WeirdPoint ascription, we’re applying it to the result of the first ascription, which does have a field named Z, so that value is used to specify the value for Z—the default value specified by WeirdPoint is not needed.

1.2.6 Constraints on Entity Types

Like all types, a constraint may be applied to an entity type using the where operator. Consider the following type definition:

type HighPoint {
  X : Number;
  Y : Number;
} where X < Y;

In this example, all values that conform to the type HighPoint are guaranteed to have an X value that is less than the Y value. That means that the following expressions:

{ X = 100, Y = 200 } in HighPoint
! ({ X = 300, Y = 200 } in HighPoint)

both evaluate to true.

Now consider the following type definitions:

type Point {
  X : Number;
  Y : Number;
}
type Visual {
  Opacity : Number;
}
type VisualPoint {
  DotSize : Number;
} where value in Point && value in Visual;

The third type, VisualPoint, names the set of entity values that have at least the numeric fields X, Y, Opacity, and DotSize.

Because it is a common desire to factor member declarations into smaller pieces that can be easily composed, M provides explicit syntax support for this. We can rewrite the VisualPoint type definition using that syntax:

type VisualPoint : Point, Visual {
  DotSize : Number;
}

To be clear, this is just shorthand for the preceding long-hand definition that used a constraint expression. Both of these definitions are equivalent to this even longer-hand definition:

type VisualPoint {
  X : Number;
  Y : Number;
  Opacity : Number;
  DotSize : Number;
}

Again, the names of the types are just ways to refer to types—the values themselves have no record of the type names used to describe them.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020