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 de.nitions, 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 de.ne a new type by explicitly enumerating those values: type PrimaryColors { "Red", "Blue", "Yellow" } This is how an enumeration is de.ned in M. Any type in M is a collection of values. For example, the types Logical and Integer8 de.ned next could be de.ned 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 speci.c type at initialization-time and is a member of the .nite set of subtypes that were speci.ed when the type was de.ned. One last type-related operation bears discussion-the type ascription operator ":". The type ascription operator asserts that a given value conforms to a speci.c 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 de.nition: 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-de.ned 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 .ve 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 .ve. 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 .rst 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 .rst 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 con.gurable to reject M program that rely on dynamic enforcement for correctness to reduce the performance and operational costs of dynamic constraint violations.