Types

A type value is a value that classifies other values. A value that is classified by a type is said to conform to that type. The M type system consists of the following kinds of types:

  • Primitive types, which classify primitive values (binary, date, datetime, datetimezone, duration, list, logical, null, number, record, text, time, type) and also include a number of abstract types (function, table, any, and none)

  • Record types, which classify record values based on field names and value types

  • List types, which classify lists using a single item base type

  • Function types, which classify function values based on the types of their parameters and return values

  • Table types, which classify table values based on column names, column types, and keys

  • Nullable types, which classifies the value null in addition to all the values classified by a base type

  • Type types, which classify values that are types

The set of primitive types includes the types of primitive values a number of abstract types, types that do not uniquely classify any values: function, table, any, and none. All function values conform to the abstract type function, all table values to the abstract type table, all values to the abstract type any, and no values to the abstract type none. An expression of type none must raise an error or fail to terminate since no value could be produced that conforms to type none. Note that the primitive types function and table are abstract because no function or table is directly of those types, respectively. The primitive types record and list are non-abstract because they represent an open record with no defined fields and a list of type any, respectively.

All types that are not members of the closed set of primitive types are collectively referred to as custom types. Custom types can be written using a type-expression:

type-expression:
      primary-expression

      type primary-type
type:
      parenthesized-expression
      primary-type
primary-type:
      primitive-type
      record-type
      list-type
      function-type
      table-type
      nullable-type
primitive-type:
one of
      any binary date datetime datetimezone duration function list logical
      none null number record table text time type

The primitive-type names are contextual keywords recognized only in a type context. The use of parentheses in a type context moves the grammar back to a regular expression context, requiring the use of the type keyword to move back into a type context. For example, to invoke a function in a type context, parentheses can be used:

type nullable ( Type.ForList({type number}) )   
// type nullable {number}

Parentheses can also be used to access a variable whose name collides with a primitive-type name:

let  record = type [ A = any ]  in  type {(record)} 
// type {[ A = any ]}

The following example defines a type that classifies a list of numbers:

type { number }

Similarly, the following example defines a custom type that classifies records with mandatory fields named X and Y whose values are numbers:

type [ X = number, Y = number ]

The ascribed type of a value is obtained using the standard library function Value.Type, as shown in the following examples:

Value.Type( 2 )                 // type number 
Value.Type( {2} )               // type list 
Value.Type( [ X = 1, Y = 2 ] )  // type record

The is operator is used to determine whether a value's type is compatible with a given type, as shown in the following examples:

1 is number          // true 
1 is text            // false 
{2} is list          // true

The as operator checks if the value is compatible with the given type, and raises an error if it is not. Otherwise, it returns the original value.

Value.Type( 1 as number )   // type number 
{2} as text                 // error, type mismatch

Note that the is and as operators only accept primitive types as their right operand. M does not provide means to check values for conformance to custom types.

A type X is compatible with a type Y if and only if all values that conform to X also conform to Y. All types are compatible with type any and no types (but none itself) are compatible with type none. The following graph shows the compatibility relation. (Type compatibility is reflexive and transitive. It forms a lattice with type any as the top and type none as the bottom value.) The names of abstract types are set in italics.

Type compatibility

The following operators are defined for type values:

Operator Result
x = y Equal
x <> y Not equal

The native type of type values is the intrinsic type type.

Primitive Types

Types in the M language form a disjoint hierarchy rooted at type any, which is the type that classifies all values. Any M value conforms to exactly one primitive subtype of any. The closed set of primitive types deriving from type any are as follows:

  • type null, which classifies the null value.
  • type logical, which classifies the values true and false.
  • type number, which classifies number values.
  • type time, which classifies time values.
  • type date, which classifies date values.
  • type datetime, which classifies datetime values.
  • type datetimezone, which classifies datetimezone values.
  • type duration, which classifies duration values.
  • type text, which classifies text values.
  • type binary, which classifies binary values.
  • type type, which classifies type values.
  • type list, which classifies list values.
  • type record, which classifies record values.
  • type table, which classifies table values.
  • type function, which classifies function values.
  • type anynonnull, which classifies all values excluding null. The intrinsic type none classifies no values.

Any Type

The type any is abstract, classifies all values in M, and all types in M are compatible with any. Variables of type any can be bound to all possible values. Since any is abstract, it cannot be ascribed to values—that is, no value is directly of type any.

List Types

Any value that is a list conforms to the intrinsic type list, which does not place any restrictions on the items within a list value.

list-type:
      { item-type }
item-type:
      type

The result of evaluating a list-type is a list type value whose base type is list.

The following examples illustrate the syntax for declaring homogeneous list types:

type { number }        // list of numbers type 
     { record }        // list of records type
     {{ text }}        // list of lists of text values

A value conforms to a list type if the value is a list and each item in that list value conforms to the list type's item type.

The item type of a list type indicates a bound: all items of a conforming list conform to the item type.

Record Types

Any value that is a record conforms to the intrinsic type record, which does not place any restrictions on the field names or values within a record value. A record-type value is used to restrict the set of valid names as well as the types of values that are permitted to be associated with those names.

record-type:
      [ open-record-marker ]
      [ field-specification-listopt ]
      [ field-specification-list , open-record-marker ]
field-specification-list:
      field-specification
      field-specification , field-specification-list
field-specification:

      optionalopt field-name field-type-specificationopt
field-type-specification:

      = field-type
field-type:
      type
open-record-marker:

      ...

The result of evaluating a record-type is a type value whose base type is record.

The following examples illustrate the syntax for declaring record types:

type [ X = number, Y = number] 
type [ Name = text, Age = number ]
type [ Title = text, optional Description = text ] 
type [ Name = text, ... ]

Record types are closed by default, meaning that additional fields not present in the fieldspecification-list are not allowed to be present in conforming values. Including the openrecord-marker in the record type declares the type to be open, which permits fields not present in the field specification list. The following two expressions are equivalent:

type record   // primitive type classifying all records 
type [ ... ]  // custom type classifying all records

A value conforms to a record type if the value is a record and each field specification in the record type is satisfied. A field specification is satisfied if any of the following are true:

  • A field name matching the specification's identifier exists in the record and the associated value conforms to the specification's type

  • The specification is marked as optional and no corresponding field name is found in the record

A conforming value may contain field names not listed in the field specification list if and only if the record type is open.

Function Types

Any function value conforms to the primitive type function, which does not place any restrictions on the types of the function's formal parameters or the function's return value. A custom function-type value is used to place type restrictions on the signatures of conformant function values.

function-type:
      function ( parameter-specification-listopt ) function-return-type
parameter-specification-list:
      required-parameter-specification-list
      required-parameter-specification-list
, optional-parameter-specification-list
      optional-parameter-specification-list
required-parameter-specification-list:
      required-parameter-specification
      required-parameter-specification
, required-parameter-specification-list
required-parameter-specification:
      parameter-specification
optional-parameter-specification-list:
      optional-parameter-specification
      optional-parameter-specification
, optional-parameter-specification-list
optional-parameter-specification:

      optional parameter-specification
parameter-specification:
      parameter-name parameter-type
function-return-type:
      assertion
assertion:

      as nullable-primitive-type

The result of evaluating a function-type is a type value whose base type is function.

The following examples illustrate the syntax for declaring function types:

type function (x as text) as number 
type function (y as number, optional z as text) as any

A function value conforms to a function type if the return type of the function value is compatible with the function type's return type and each parameter specification of the function type is compatible to the positionally corresponding formal parameter of the function. A parameter specification is compatible with a formal parameter if the specified parameter-type type is compatible with the type of the formal parameter and the parameter specification is optional if the formal parameter is optional.

Formal parameter names are ignored for the purposes of determining function type conformance.

Table types

A table-type value is used to define the structure of a table value.

table-type:
      table row-type
row-type:

      [ field-specification-list ]

The result of evaluating a table-type is a type value whose base type is table.

The row type of a table specifies the column names and column types of the table as a closed record type. So that all table values conform to the type table, its row type is type record (the empty open record type). Thus, type table is abstract since no table value can have type table's row type (but all table values have a row type that is compatible with type table's row type). The following example shows the construction of a table type:

type table [A = text, B = number, C = binary] 
// a table type with three columns named A, B, and C 
// of column types text, number, and binary, respectively

A table-type value also carries the definition of a table value's keys. A key is a set of column names. At most one key can be designated as the table's primary key. (Within M, table keys have no semantic meaning. However, it is common for external data sources, such as databases or OData feeds, to define keys over tables. Power Query uses key information to improve performance of advanced functionality, such as cross-source join operations.)

The standard library functions Type.TableKeys, Type.AddTableKey, and Type.ReplaceTableKeys can be used to obtain the keys of a table type, add a key to a table type, and replace all keys of a table type, respectively.

Type.AddTableKey(tableType, {"A", "B"}, false) 
// add a non-primary key that combines values from columns A and B 
Type.ReplaceTableKeys(tableType, {}) 
// returns type value with all keys removed

Nullable types

For any type T, a nullable variant can be derived by using nullable-type:

nullable-type:
      nullable type

The result is an abstract type that allows values of type T or the value null.

42 is nullable number             // true null is
nullable number                   // true

Ascription of type nullable T reduces to ascription of type null or type T. (Recall that nullable types are abstract and no value can be directly of abstract type.)

Value.Type(42 as nullable number)       // type number
Value.Type(null as nullable number)     // type null

The standard library functions Type.IsNullable and Type.NonNullable can be used to test a type for nullability and to remove nullability from a type.

The following hold (for any type T):

  • type T is compatible with type nullable T
  • Type.NonNullable(type T) is compatible with type T

The following are pairwise equivalent (for any type T):

    type nullable any
    any

    Type.NonNullable(type any)
    type anynonnull

    type nullable none
    type null

    Type.NonNullable(type null)
    type none

    type nullable nullable T
    type nullable T

    Type.NonNullable(Type.NonNullable(type T))
    Type.NonNullable(type T)

    Type.NonNullable(type nullable T)
    Type.NonNullable(type T)

    type nullable (Type.NonNullable(type T))
    type nullable T

Ascribed type of a value

A value's ascribed type is the type to which a value is declared to conform. When a value is ascribed a type, only a limited conformance check occurs. M does not perform conformance checking beyond a nullable primitive type. M program authors that choose to ascribe values with type definitions more complex than a nullable primitive-type must ensure that such values conform to these types.

A value may be ascribed a type using the library function Value.ReplaceType. The function either returns a new value with the type ascribed or raises an error if the new type is incompatible with the value's native primitive type. In particular, the function raises an error when an attempt is made to ascribe an abstract type, such as any.

Library functions may choose to compute and ascribe complex types to results based on the ascribed types of the input values.

The ascribed type of a value may be obtained using the library function Value.Type. For example:

Value.Type( Value.ReplaceType( {1}, type {number} ) 
// type {number}

Type equivalence and compatibility

Type equivalence is not defined in M. Any two type values that are compared for equality may or may not return true. However, the relation between those two types (whether true or false) will always be the same.

Compatibility between a given type and a nullable primitive type can be determined using the library function Type.Is, which accepts an arbitrary type value as its first and a nullable primitive type value as its second argument:

Type.Is(type text, type nullable text)  // true 
Type.Is(type nullable text, type text)  // false 
Type.Is(type number, type text)         // false 
Type.Is(type [a=any], type record)      // true 
Type.Is(type [a=any], type list)        // false

There is no support in M for determining compatibility of a given type with a custom type.

The standard library does include a collection of functions to extract the defining characteristics from a custom type, so specific compatibility tests can be implemented as M expressions. Below are some examples; consult the M library specification for full details.

Type.ListItem( type {number} ) 
  // type number 
Type.NonNullable( type nullable text ) 
  // type text 
Type.RecordFields( type [A=text, B=time] ) 
  // [ A = [Type = type text, Optional = false], 
  //   B = [Type = type time, Optional = false] ] 
Type.TableRow( type table [X=number, Y=date] ) 
  // type [X = number, Y = date] 
Type.FunctionParameters(
        type function (x as number, optional y as text) as number) 
  // [ x = type number, y = type nullable text ] 
Type.FunctionRequiredParameters(
        type function (x as number, optional y as text) as number) 
  // 1 
Type.FunctionReturn(
        type function (x as number, optional y as text) as number) 
  // type number