Tipos

Un valor de tipo es un valor que clasifica otros valores. Se dice que un valor que está clasificado por un tipo se ajusta a ese tipo. El sistema de tipos de M consta de los tipos siguientes:

  • Tipos primitivos, que clasifican valores primitivos (binary, date, datetime, datetimezone, duration, list, logical, null, number, record, text, time, type) e incluyen también una serie de tipos abstractos (function, table, any, anynonnull y none)

  • Tipos de registro, que clasifican valores de registro en función de nombres de campo y tipos de valor

  • Tipos de lista, que clasifican listas mediante un tipo base de un solo elemento

  • Tipos de función, que clasifican valores de función según los tipos de sus parámetros y los valores devueltos

  • Tipos de tabla, que clasifican valores de tabla en función de nombres de columna, tipos de columna y claves

  • Tipos que aceptan valores NULL, que clasifican el valor NULL, además de todos los valores clasificados por un tipo base

  • Tipos de tipo, que clasifican valores que son tipos

El conjunto de tipos primitivos incluye los tipos de valores primitivos, y un número de tipos abstractos, que son tipos que no clasifican de forma única los valores: function, table, any, anynonnull y none. Todos los valores de función se ajustan al tipo abstracto function, todos los valores de tabla al tipo abstracto table, todos los valores al tipo abstracto any, todos los valores que no son null al tipo abstracto anynonnull y ningún valor al tipo abstracto none. Una expresión de tipo none debe generar un error o no finalizar porque no se ha podido generar ningún valor que se ajuste al tipo none. Tenga en cuenta que los tipos primitivos function y table son abstractos porque ninguna función o tabla es directamente de esos tipos, respectivamente. Los tipos primitivos record y list no son abstractos porque representan un registro abierto sin campos definidos y una lista de tipo any, respectivamente.

Todos los tipos que no son miembros del conjunto cerrado de tipos primitivos más sus homólogos que aceptan valores null se denominan colectivamente tipos personalizados. Los tipos personalizados se pueden escribir mediante type-expression:

type-expression:
      primary-expression

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

Los nombres de primitive-type son palabras clave contextuales que solo se reconocen en el contexto de un tipo. El uso de paréntesis en el contexto de un tipo devuelve la gramática al contexto de una expresión regular, lo que requiere el uso de la palabra clave type para volver al contexto de un tipo. Por ejemplo, para invocar una función en el contexto de un tipo, se pueden usar paréntesis:

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

Los paréntesis también se pueden usar para acceder a una variable cuyo nombre entra en conflicto con un nombre de tipo primitivo:

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

En el ejemplo siguiente se define un tipo que clasifica una lista de números:

type { number }

Del mismo modo, en el ejemplo siguiente se define un tipo personalizado que clasifica los registros con los campos obligatorios X e Y cuyos valores son números:

type [ X = number, Y = number ]

El tipo atribuido de un valor se obtiene mediante la función de la biblioteca estándar Value.Type, como se muestra en los ejemplos siguientes:

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

El operador is se usa para determinar si el tipo de un valor es compatible con un tipo determinado, como se muestra en los ejemplos siguientes:

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

El operador as comprueba si el valor es compatible con el tipo especificado y genera un error si no lo es. De lo contrario, devuelve el valor original.

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

Tenga en cuenta que los operadores is y as solo aceptan tipos primitivos que aceptan valores null como operando derecho. En M no se proporcionan medios para comprobar la conformidad con tipos personalizados.

Un tipo X es compatible con un tipo Y solo si todos los valores que se ajustan a X también se ajustan a Y. Todos los tipos son compatibles con el tipo any y ningún tipo (menos el propio none) es compatible con el tipo none. En el gráfico siguiente se muestra la relación de compatibilidad. (la compatibilidad de tipos es reflexiva y transitiva. Forma un enlace con el tipo any en la parte superior y el tipo none como valor inferior). Los nombres de los tipos abstractos se establecen en cursiva.

Compatibilidad de tipos

Los operadores siguientes se definen para los valores de tipo:

Operador Resultado
x = y Igual
x <> y No igual a
x ?? y Coalesce

El tipo nativo de los valores de tipo es el tipo intrínseco type.

Tipos primitivos

Los tipos del lenguaje M forman una jerarquía no contigua cuya raíz es el tipo any, el que clasifica todos los valores. Cualquier valor de M se ajusta a exactamente un subtipo primitivo de any. A continuación se muestra el conjunto cerrado de tipos primitivos que derivan del tipo any:

  • type null, que clasifica el valor NULL.
  • type logical, que clasifica los valores true y false.
  • type number, que clasifica valores numéricos.
  • type time, que clasifica valores de hora.
  • type date, que clasifica valores de fecha.
  • type datetime, que clasifica valores de fecha y hora.
  • type datetimezone, que clasifica valores de fecha, hora y zona horaria.
  • type duration, que clasifica valores de duración.
  • type text, que clasifica valores de texto.
  • type binary, que clasifica valores binarios.
  • type type, que clasifica valores de tipo.
  • type list, que clasifica valores de lista.
  • type record, que clasifica valores de registro.
  • type table, que clasifica valores de tabla.
  • type function, que clasifica valores de función.
  • type anynonnull, que clasifica todos los valores menos NULL.
  • type none, que no clasifica valores.

Tipo any

El tipo any es abstracto, clasifica todos los valores de M y todos los tipos de M son compatibles con any. Las variables de tipo any se pueden enlazar a todos los valores posibles. Como any es abstracto, no se puede atribuir a valores; es decir, ningún valor es directamente de tipo any.

Tipos de lista

Cualquier valor que sea una lista se ajusta al tipo intrínseco list, que no impone ninguna restricción sobre los elementos dentro de un valor de lista.

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

El resultado de evaluar list-type es un valor de tipo de lista cuyo tipo base es list.

En los ejemplos siguientes se muestra la sintaxis para declarar tipos de lista homogéneos:

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

Un valor se ajusta a un tipo de lista si el valor es una lista y cada elemento de ese valor de lista se ajusta al tipo de elemento del tipo de lista.

El tipo de elemento de un tipo de lista indica un enlace: todos los elementos de una lista conforme se ajustan al tipo de elemento.

Tipos de registro

Cualquier valor que sea un registro se ajusta al tipo intrínseco record, que no impone ninguna restricción sobre los nombres de campo ni los valores dentro de un valor de registro. Un valor de tipo de registro se usa para restringir el conjunto de nombres válidos, así como los tipos de valores que se pueden asociar a esos nombres.

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:
      tipo
open-record-marker:

      ...

El resultado de evaluar record-type es un valor de tipo cuyo tipo base es record.

En los ejemplos siguientes se muestra la sintaxis para declarar tipos de registro:

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

De forma predeterminada, los tipos de registro son cerrados, lo que significa que no se permiten campos adicionales que no están presentes en fieldspecification-list en los valores de conformidad. Al incluir openrecord-marker en el tipo de registro, se declara que el tipo es abierto, lo que permite campos no presentes en la lista de especificación de campos. Las dos expresiones siguientes son equivalentes:

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

Un valor se ajusta a un tipo de registro si el valor es un registro y se cumple cada especificación de campo en el tipo de registro. Se cumple una especificación de campo si se cumple alguna de las condiciones siguientes:

  • Un nombre de campo que coincide con el identificador de la especificación existe en el registro y el valor asociado se ajusta al tipo de la especificación.

  • La especificación está marcada como opcional y no se encuentra ningún nombre de campo correspondiente en el registro

Un valor compatible puede contener nombres de campo que no aparecen en la lista de especificación de campos si el tipo de registro está abierto.

Tipos de función

Cualquier valor de función se ajusta al tipo primitivo function, que no impone ninguna restricción en los tipos de los parámetros formales de la función ni en el valor que devuelve. Se usa un valor de tipo de función personalizado para imponer restricciones de tipo en las firmas de los valores de función compatibles.

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:

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

      asnullable-primitive-type

El resultado de evaluar function-type es un valor de tipo cuyo tipo base es function.

En los ejemplos siguientes se muestra la sintaxis para declarar tipos de función:

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

Un valor de función se ajusta a un tipo de función si el tipo del valor devuelto de función es compatible con el tipo de valor devuelto del tipo de función y cada especificación de parámetro del tipo de función es compatible con el parámetro formal correspondiente posicionalmente de la función. Una especificación de parámetro es compatible con un parámetro formal si el tipo parameter-type especificado es compatible con el tipo del parámetro formal y la especificación de parámetro es opcional si el parámetro formal es opcional.

Los nombres de parámetros formales se omiten con el fin de determinar la compatibilidad del tipo de función.

Especificar un parámetro como opcional implícitamente hace que su tipo acepte valores null. La acción siguiente crea tipos de función idénticos:

type function (optional x as text) as any
type function (optional x as nullable text) as any

Tipos de tablas

Un valor de tipo de tabla se usa para definir la estructura de un valor de tabla.

table-type:
      tablerow-type
row-type:

      [field-specification-listopt]

El resultado de evaluar table-type es un valor de tipo cuyo tipo base es table.

El valor row type de una tabla especifica los nombres y los tipos de columna de la tabla como un tipo de registro cerrado. Para que todos los valores de tabla se ajusten al tipo table, su tipo de fila es record (el tipo de registro abierto vacío). Por tanto, el tipo de tabla es abstracto ya que ningún valor de tabla puede tener el tipo de fila de table del tipo (pero todos los valores de tabla tienen un tipo de fila que es compatible con el tipo de fila de table del tipo). En el ejemplo siguiente se muestra la construcción de un tipo de tabla:

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

Un valor de tipo de tabla también incluye la definición de las claves de un valor de tabla. Una clave es un conjunto de nombres de columna. Como máximo, se puede designar una clave como clave principal de la tabla. (en M, las claves de tabla no tienen ningún significado semántico. Pero es habitual que los orígenes de datos externos, como las bases de datos o las fuentes de OData, definan claves antes que tablas. En Power Query se usa la información de clave para mejorar el rendimiento de las funciones avanzadas, como las operaciones de combinación entre orígenes).

Las funciones de la biblioteca estándar Type.TableKeys, Type.AddTableKey y Type.ReplaceTableKeys se pueden usar para obtener las claves de un tipo de tabla, agregar una clave a un tipo de tabla y reemplazar todas las claves de un tipo de tabla, respectivamente.

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

Tipos que aceptan valores NULL

Para cualquier type T, se puede derivar una variante que acepta valores NULL mediante nullable-type:

nullable-type:
      nullabletype

El resultado es un tipo abstracto que permite valores de tipo T o el valor null.

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

La atribución de type nullableT se reduce a la de type null o typeT. (Recuerde que los tipos que aceptan valores NULL son abstractos y que ningún valor puede ser directamente de tipo abstracto).

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

Las funciones de la biblioteca estándar Type.IsNullable y Type.NonNullable se pueden usar para probar la nulabilidad de un tipo y para quitarla de un tipo.

Sucede lo siguiente (para cualquier type T):

  • type T es compatible con type nullable T
  • Type.NonNullable(type T) es compatible con type T

Los elementos siguientes son equivalentes en pares (para cualquier 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

Tipo atribuido de un valor

El tipo atribuido de un valor es el tipo al que se declara que el valor se ajusta.

Un valor se puede atribuir a un tipo mediante la función de biblioteca Value.ReplaceType. Esta función devuelve un nuevo valor con el tipo atribuido o genera un error si el nuevo tipo es incompatible con el valor.

Cuando un valor se atribuye a un tipo, solo se produce una comprobación de compatibilidad limitada:

  • El tipo que se describe debe ser no abstracto, que no acepta valores NULL y compatible con el tipo primitivo intrínseco (nativo) del valor.
  • Cuando se describe un tipo personalizado que define la estructura, debe coincidir con la estructura del valor.
    • Para los registros: el tipo debe cerrarse y definir el mismo número de campos que el valor, y no debe contener ningún campo opcional. (Los nombres de campo y los tipos de campo del tipo reemplazarán a los asociados actualmente con el registro. Pero los valores de campo existentes no se comprobarán con los nuevos tipos de campo).
    • Para las tablas: el tipo debe definir el mismo número de columnas que el valor. (Los nombres de columna y los tipos de columna del tipo reemplazarán a los asociados actualmente a la tabla. Pero los valores de columna existentes no se comprobarán con los nuevos tipos de columna).
    • Para las funciones: el tipo debe definir el mismo número de parámetros necesarios, así como el mismo número de parámetros opcionales, que el valor. (El parámetro del tipo y las aserciones de devolución, así como sus nombres de parámetro, reemplazarán a los asociados con el tipo actual del valor de la función. Pero las nuevas aserciones no tendrán ningún efecto en el comportamiento real de la función).
    • Para las listas: el valor debe ser una lista. (Pero los elementos de lista existentes no se comprobarán con el nuevo tipo de elemento).

Las funciones de biblioteca pueden optar por calcular y atribuir tipos complejos a los resultados en función de los tipos atribuidos de los valores de entrada.

El tipo atribuido de un valor se puede obtener mediante la función de biblioteca Value.Type. Por ejemplo:

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

Equivalencia y compatibilidad de tipos

La equivalencia de tipos no está definida en M. Opcionalmente, una implementación M puede optar por usar sus propias reglas para realizar comparaciones de igualdad entre valores de tipo. La comparación de la igualdad de dos valores de tipo debe evaluarse como true si la implementación las considera idénticas o, en caso contrario, como false. En cualquier caso, la respuesta devuelta debe ser coherente si se comparan dos valores iguales de forma repetida. Tenga en cuenta que, dentro de una implementación determinada, comparar algunos valores de tipo idénticos (como (type text) = (type text)) puede devolver true, pero no si se comparan otros valores (como (type [a = text]) = (type [a = text])).

La compatibilidad entre un tipo determinado y un tipo primitivo que acepta valores NULL se puede determinar mediante la función de biblioteca Type.Is, que acepta un valor de tipo arbitrario como su primer argumento y un valor de tipo primitivo que acepta valores NULL como segundo:

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

En M no hay ninguna forma de determinar la compatibilidad de un tipo determinado con un tipo personalizado.

La biblioteca estándar incluye una colección de funciones para extraer las características de definición de un tipo personalizado, de modo que se pueden implementar pruebas de compatibilidad específicas como expresiones de M. A continuación se muestran algunos ejemplos; consulte la especificación de la biblioteca de M para obtener todos los detalles.

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