1.2.1 Collection Types M de.nes 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 speci.es both the type of element and the acceptable element count. The element count is typically speci.ed 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 de.nitions: 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 post.x 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 .elds or as computed values. The value of a .eld 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 .elds. In M, entity types are open in that entity values that conform to the type may contain .elds 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 .elds named X and Y. Most entity types contain one or more .eld declarations. At a minimum, a .eld declaration states the name of the expected .eld: type Point { X; Y; } This type de.nition describes the set of entities that contain at least .elds named X and Y irrespective of the values of those .elds. 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 .elds-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 .elds 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 de.nitions of NumericPoint and Point are independent.