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
ynone
)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
type
tipo principal
type:
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.
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
.
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.
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
.
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.
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:
optional
opt field-name field-type-specificationopt
field-type-specification:
=
tipo de campo
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.
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:
optional
parameter-specification
parameter-specification:
parameter-name parameter-type
function-return-type:
assertion
assertion:
as
Nullable-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
Un valor de tipo de tabla se usa para definir la estructura de un valor de tabla.
table-type:
table
tipo de fila
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
Para cualquier type T
, se puede derivar una variante que acepta valores NULL mediante nullable-type:
nullable-type:
nullable
tipo
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 asignación de type nullable
T se reduce a la asignación de type null
o type
T. (Recuerde que los tipos que aceptan valores NULL son abstractos y 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 contype nullable T
Type.NonNullable(type T)
es compatible contype 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
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}
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