类型类型(对属于类型的值进行分类)
一组基元类型包含基元值的类型和许多抽象类型,这些类型不对任何值进行唯一分类:
function
、
table
、
any
、
anynonnull
和
none
。 所有函数值都符合抽象类型
function
,所有表值都符合抽象类型
table
,所有值都符合抽象类型
any
,所有非 null 值都符合抽象类型
anynonnull
,没有值符合抽象类型
none
。 类型为
none
的表达式必须引发错误或无法终止,因为无法生成符合类型
none
的值。 请注意,基元类型
function
和
table
是抽象的,因为任何函数或表都不直接属于这些类型。 基元类型
record
和
list
是非抽象的,因为它们分别表示一个没有定义字段的 open 记录和一个 any 类型的列表。
所有不属于封闭基元类型集成员的类型及其可为空对应对象的类型统称为自定义类型。 可以使用
type-expression
编写自定义类型:
type-expression:
primary-expression
type
primary-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
基元类型名称是上下文关键字,仅在类型上下文中识别 。 在类型上下文中使用括号将语法移回正则表达式上下文,需要使用关键字类型才能返回到类型上下文。 例如,要在类型上下文中调用函数,可以使用括号:
type nullable ( Type.ForList({type number}) )
// type nullable {number}
括号还可用于访问名称与基元类型名称相冲突的变量:
let record = type [ A = any ] in type {(record)}
// type {[ A = any ]}
以下示例定义了对数字列表进行分类的类型:
type { number }
同样,以下示例定义了一个自定义类型,该类型使用名为 X
和 Y
的必需字段(其值为数字)对记录进行分类:
type [ X = number, Y = number ]
值的归属类型是使用标准库函数 Value.Type 获取的,如以下示例所示:
Value.Type( 2 ) // type number
Value.Type( {2} ) // type list
Value.Type( [ X = 1, Y = 2 ] ) // type record
is
运算符用于确定值的类型是否与给定类型兼容,如以下示例所示:
1 is number // true
1 is text // false
{2} is list // true
as
运算符检查该值是否与给定类型兼容,如果不兼容,则引发错误。 否则,它将返回原始值。
Value.Type( 1 as number ) // type number
{2} as text // error, type mismatch
请注意,is
和 as
运算符仅接受可为空的基元类型作为其正确的操作数。 M 不提供用于检查值是否符合自定义类型的方法。
当且仅当符合 X
的所有值也符合 Y
时,类型 X
与类型 Y
兼容。 所有类型都与类型 any
兼容,没有类型(除了 none
本身)与类型 none
兼容。 下图显示了兼容性关系。 (类型总是与其自身兼容,且类型兼容性可传递。它形成了一个点阵,类型 any
在最上,类型 none
在最下。)抽象类型的名称设置为斜体。
为类型值定义了以下运算符:
类型值的本机类型是内部类型 type
。
M 语言中的类型形成一个源于类型 any
的不相交的层次结构,该类型对所有值进行分类。 任何 M 值都符合 any
的恰好一种基元子类型。 从类型 any
派生的基元类型闭集如下:
type null
可以对 null 值进行分类。
type logical
可以将值 true 和 false 进行分类。
type number
可以对数字值进行分类。
type time
可以对时间值进行分类。
type date
可以对日期值进行分类。
type datetime
可以对日期/时间值进行分类。
type datetimezone
可以对时区值进行分类。
type duration
可以对持续时间值进行分类。
type text
可以对文本值进行分类。
type binary
可以对二进制值进行分类。
type type
可以对类型值进行分类。
type list
可以对列表值进行分类。
type record
可以对记录值进行分类。
type table
可以对表值进行分类。
type function
可以对函数值进行分类。
type anynonnull
可以对 null 以外的所有值进行分类。
type none
不对任何值进行分类。
类型 any
是抽象的,它对 M 中的所有值进行分类,并且 M 中的所有类型都与 any
兼容。 any
类型的变量可以绑定到所有可能的值。 由于 any
是抽象的,因此不能将其归属于值,也就是说,没有值直接属于 any
类型。
任何属于列表的值都符合内部类型 list
,该类型不会对列表值中的项施加任何限制。
list-type:
{
item-type}
item-type:
计算 list-type 的结果是一个基类型为 list
的列表类型值。
以下示例演示了用于声明同类列表类型的语法:
type { number } // list of numbers type
{ record } // list of records type
{{ text }} // list of lists of text values
如果值是一个列表,并且该列表值中的每一项都符合列表类型的项类型,则该值符合列表类型。
列表类型的项类型暗示着一种绑定:符合的列表中所有项都符合项类型。
任何记录值都符合内部类型 record,该类型不会对记录值中的字段名称或值施加任何限制。 使用 record-type 值限制有效名称集,以及允许与这些名称相关联的值类型。
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:
=
field-type
field-type:
open-record-marker:
计算 record-type 的结果是一个基类型为 record
的类型值。
以下示例演示了用于声明记录类型的语法:
type [ X = number, Y = number]
type [ Name = text, Age = number ]
type [ Title = text, optional Description = text ]
type [ Name = text, ... ]
记录类型默认为 closed,这意味着符合值中不可以存在 fieldspecification-list 中没有的字段。 在记录类型中包含 openrecord-marker,可将该类型声明为 open,允许其包含字段规范列表中没有的字段。 以下两个表达式等效:
type record // primitive type classifying all records
type [ ... ] // custom type classifying all records
如果值是一条记录,且满足记录类型中的每项字段规范,则该值符合记录类型。 若以下任何一项属实,则满足字段规范:
记录中存在与规范的标识符匹配的字段名称,并且关联的值符合规范的类型
该规范标记为可选,并且在记录中没有相应的字段名称
当且仅当记录类型为 open 时,符合值可以包含字段规范列表中未列出的字段名。
任何函数值都符合基元类型 function
,它不对函数的形式参数的类型或函数的返回值施加任何限制。 自定义 function-type 值用于对共形函数值的签名施加类型限制。
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
计算 function-type 的结果是一个基类型为 function
的类型值。
以下示例说明了声明函数类型的语法:
type function (x as text) as number
type function (y as number, optional z as text) as any
如果函数值的返回类型与函数类型的返回类型兼容,并且函数类型的每个参数规范与函数的位置对应的形式参数兼容,则函数值符合函数类型。 如果指定的 parameter-type 类型与形式参数的类型兼容,并且如果形式参数是可选的,则参数规范与形式参数兼容。
为了确定函数类型符合性,会忽略形式参数名称。
使用 table-type 值来定义表值的结构。
table-type:
table
row-type
row-type:
[
field-specification-list]
计算 table-type 的结果是一个基类型为 table
的类型值。
表的行类型将表的列名和列类型指定为封闭记录类型。 从而所有表值都符合类型 table
,其行类型为类型 record
(空的开放记录类型)。 因此,类型表是抽象的,这是因为没有表值可以具有 table
的行类型(但是所有表值都具有与类型 table
的行类型兼容的行类型)。 以下示例显示了表类型的构造:
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
表类型的值还具有表值的键的定义。 一个键就是一组列名称。 最多只有一个键可以指定为表的主键。 (在 M 中,表键没有语义意义。但是,外部数据源(如数据库或 OData 源)通常通过表定义键。Power Query 使用键信息来增强高级功能的性能,例如跨源联接操作。)
标准库函数 Type.TableKeys
、Type.AddTableKey
和 Type.ReplaceTableKeys
分别可用于获取表类型的键,向表类型添加一个键,替换表类型的所有键。
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
可为 null 的类型
对于任何 type T
,可以使用 nullable-type 来派生可为 null 的变量:
nullable-type:
nullable
type
结果是一个抽象类型,允许类型 T 的值或值 null
。
42 is nullable number // true null is
nullable number // true
对 type nullable
T 的描述归结为对 type null
或 type
T 的描述。(回想一下,可为 null 的类型是抽象的,任何值都不能直接为抽象类型。)
Value.Type(42 as nullable number) // type number
Value.Type(null as nullable number) // type null
标准库函数 Type.IsNullable
和 Type.NonNullable
可用于测试类型是否可为 null,并使类型不可为 null。
以下条件适用(对于任何 type T
):
type T
与 type nullable T
兼容
Type.NonNullable(type T)
与 type T
兼容
以下是成对等效(对于任何 type T
):
type nullable 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
值的归属类型
值的归属类型是声明一个值是否符合的类型。
使用库函数 Value.ReplaceType
可以将值归属于类型。 该函数会返回具有已归属类的新值,或在新类型与值不兼容的情况下引发错误。
当值归属为某一类型时,只会进行有限的符合性检查:
值所归属的类型必须是非抽象且非 null 的,并要与值的内部(本机)基元类型兼容。
如果值归属于定义结构的自定义类型,则该类型必须与值的结构匹配。
对于记录:该类型必须是限定好的,且必须定义与值相同的字段数,不得包含任何可选字段。 (该类型的字段名称和字段类型将替换当前与记录关联的字段名称和字段类型。但系统不会根据新字段类型检查现有字段值。)
对于表:该类型必须定义与值相同的列数。 (该类型的列名和列类型将替换当前与表关联的列名和列类型。但系统不会根据新列类型检查现有列值。)
对于函数:该类型必须定义与值数量相同的必需参数以及可选参数。 (该类型的参数和返回断言及其参数名称将替换与函数值的当前类型关联的相应内容。但新的断言不会影响函数的当前行为。)
对于列表:该值必须是列表。 (但系统不会根据新项类型检查现有列表项。)
库函数可以根据输入值的归属类型来选择计算复杂类型并将其归属于结果。
可以使用库函数 Value.Type
获得值的归属类型。 例如:
Value.Type( Value.ReplaceType( {1}, type {number} )
// type {number}
类型等效性和兼容性
M 中未定义类型等效性。M 实现可以选择使用自己的规则来比较类型值是否相等。 比较两个类型值是否相等,如果实现认为它们相同,则计算结果应为 true
,否则为 false
。 在任一情况下,如果两个值进行重复比较,则返回的响应必须一致。 请注意,在给定的实现中,比较一些相同类型的值(如 (type text) = (type text)
)可能会返回 true
,而比较其他类型的值(如 (type [a = text]) = (type [a = text])
)可能不会返回该值。
可以使用库函数 Type.Is
来确定指定类型和可为 null 的基元类型之间的兼容性,该函数接受任意类型值作为其第一个参数,接受可为 null 的基元类型值作为其第二个参数:
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
M 中不支持确定指定类型与自定义类型的兼容性。
标准库确实包含一个函数集合,用于从自定义类型中提取定义特征,因此特定的兼容性测试可以实现为 M 表达式。 以下是一些示例;有关详细信息,请参阅 M 库规范。
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)
Type.FunctionReturn(
type function (x as number, optional y as text) as number)
// type number