类型
类型
类型是对值的分类,类型值是保存值类型的值。
原生类型(primitive type)包括binary
、date
、datetime
、datetimezone
、duration
、logical
、list
、null
、number
、record
、text
、time
、type
、function
、table
、any
、none
。其中function
、table
、any
、none
又称为抽象类型,因为它们不能对任意值进行唯一分类。
任何表或函数都不直接属于抽象类型function
或table
,都是继承于function
和table
的子类型。
any
没有具体值,M中的所有值都可以归类到any
类型,所有类型都与any
类型兼容。
none
没有具体值,一旦表达式试图产生none
类型的值,将会引发错误或无法终止。
所有不属于原生类型的类型统称为自定义类型。自定义类型语法如下:
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
原生类型名称是仅能在类型上下文中识别的上下文关键字(即只能配合type
使用)。
在类型上下文中使用圆括号将语法移回正则表达式上下文,需要使用type
关键字移回类型上下文。例如:如果在类型上下文中调用函数(获取类型),可以使用括号:
// 不存在该函数
type nullable (Type.ForList({type number}))
圆括号还可以访问与原始类型名称相冲突的变量:
let record = type [A = any] in type {(record)}
Value.Type
可以获取值的类型。因为显示问题,类型值只会显示类型名称。
// type number,显示为number
Value.Type(1)
// type {any},显示为list
Value.Type({1, 2})
// 获取列表的项类型
Type.ListItem(Value.Type({1, 2}))
is
运算符可以检查左侧值是否兼容右侧类型。
1 is number
1 is any
{1..3} is any
as
运算符检查左侧值是否兼容右侧类型,如果兼容返回左侧值,否则引发错误。
1 as number
1 as text
1 as any
=
可以检查类型是否相等,而is
和as
检查是否兼容。
Value.Type(1) = type number
1 is number
Value.Type(1) = type any
1 is any
重要
下文会经常提到“兼容”,兼容是一种属于、归属的关系,它要和“相等”、“等于”做区分。
1
是number
类型的值,我们也可以说1
的类型值等于type number
,即Value.Type(1) = type number
。但是我们不能说1
是any
类型的值,1
只是兼容于any
类型,即1
的类型和any
兼容。
“是”虽然有属于的意思,但是通常我们更加倾向于“是”表示最近的归属关系,所以我们说1
是number
类型,但不是any
类型。
any类型
any
类型是抽象类型,它可以对M中的所有值进行归类,M中的所有类型都与any
类型兼容。因为any
是抽象的,any
类型没有具体值,不能有值直接属于any
类型(即不能有具体值的类型等于any
类型)。
列表类型
任何列表值都属于内部类型list
,该类型不会对列表中的项施加限制。
list-type:
{
item-type }
item-type:
type
列表的项类型表示一种限制:符合要求的列表,它的每个项类型都符合设置的项类型。
// {1, 2, 3}
type { number }
// {\{"A", "B"}, {"U"}\}
type { { text } }
原始类型list
(或叫内部类型list
)兼容所有列表。
{1, 2, 3} is list
Value.ReplaceType({1..3}, type {text}) is list
type list
等于type {any}
。
type {any} = type list
默认情况下列表项的类型是any
。
Type.ListItem(Value.Type({1..3}))
设置列表类型并不会影响实际存入值的类型,它只是一种预期和暗示。
Value.ReplaceType({1..3}, type {text})
记录类型
任何记录值都符合内部类型record
,自定义的记录类型不会对字段名或字段值施加任何限制。记录类型值(表示记录类型的值)用于限制有效名称的集合以及允许这些名称关联的值的类型。
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:
type
open-record-marker:
...
记录类型定义
type [a = number, b = any]
type [a = number, optional b = any]
type [a = number, ...]
记录类型分为开放(open)和闭合(closed)两种,开放是指是否允许出现字段列表中不存在的字段名。
目前Power BI和Excel中只能定义含...
和optional
的类型,并不能将它们赋予记录值,会报错。但是可以在表类型中对空表(无行列)使用。
Value.ReplaceType([x=1], (type [x=number, ...]))
默认情况下定义的记录都是闭合的,所以不能存在open-record-marker
。
Type.IsOpenRecord(Value.Type([A=1]))
type record
等于type [...]
。
type record = type [...]
默认情况下,字段名是记录中已有的名字,字段值是any
类型。
Type.RecordFields(Value.Type([A=1, B=2]))
设置记录类型值并不影响实际存入的字段名和字段值。
Value.ReplaceType([A=1, B=2], type [x=text, y=any])
替换记录类型后,读取其记录类型时,将会返回新的记录类型,而非原始的。
Type.RecordFields(Value.Type(Value.ReplaceType([A=1, B=2], type [x=text, y=any])))
如果值是记录且满足记录类型中每项字段规范,则该值符合记录类型。若以下任一项满足则满足字段规范:
- 记录中存在与规范的标识符匹配的字段名。并且关联值符合规定的类型。
- 规范中被标记为可选,并且记录中不存在对应的字段名。
当且仅当记录类型为开放时,符合要求的值可以包含未在字段规范列中列出的字段名称。
函数类型
所有函数值都属于内部类型function
,函数类型不对形参和返回值施加任何限制。自定义函数类型值用于对符合函数值的签名施加类型限制。
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
兼容所有函数值。
((x) => x + 1) is function
函数形参和返回值不声明类型时,为any
类型,即可以传入所有类型的实参并返回所有类型。
Type.FunctionParameters(Value.Type((a) => a + 1))
函数类型和函数值实际定义的形参名称可以不同,可视化界面会显示当前有效的名称(下面的函数会显示”一参“而非”a“)和类型。
Value.ReplaceType((a as text) => a + 1, type function (一参 as text) as any)
类型不同时可以正常定义,但是在调用时可能因为函数内部的计算报错。
// 可视化界面会提示“一参”是文本,但是文本不能+1,所以调用会报错
Value.ReplaceType((a as number) => a + 1, type function (一参 as text) as any)
表类型
表类型定义
table-type:
table
row-type
row-type:
[
field-specification-list ]
表的行类型(row type
)是用以指定表的列名和列类型的闭合记录类型。所有的表都符合table
类型,它的行类型是记录类型(空的开放记录类型)。因此,表类型是抽象的,因为没有表值具有类型表的行类型(但是所有表值都具有类型表的行类型兼容的行类型)。
定义表类型:
type table [A = number, B = text]
也可配合#table
使用。
#table(
type table [A = number, B = text],
{
{1, "X"},
{2, "Y"}
}
)
表类型并不影响表中实际值的类型。
#table(
type table [A = number, B = number],
{
{1, "X"},
{2, "Y"}
}
)
type table
等于type table [...]
。
type table [...] = type table
Value.Type(Value.ReplaceType(#table({}, {}), type table [...])) = type table
表值还具有键的定义。一个键就是一组列名称。最多只能指定一个键为表的主键(primary key)(表键在M中没有任何语法含义,但是外部数据源通常在表上定义键。M使用键来增强高级功能的性能,例如:跨源联结操作)。
标准库函数Type.TableKeys
、Type.AddTableKey
和Type.ReplaceTableKeys
分别用于取表类型的键、向表类型添加一个键和替换表类型的所有键。
nullable类型
对于任何type T
,可以使用nullable-type来派生可以为null
的变量。
nullable-type:
nullable
type
nullable-type的结果是一个抽象类型,它允许类型为T
的值或null
值。
type nullable T
可以简化为type null
或type T
。(type nullable T
是抽象的,没有值直接为抽象类型)。
// type number
Value.Type(42 as nullable number)
// type null
Value.Type(null as nullable number)
相关信息
抽象类型:不存在一个具体值直接属于原始类型的类型。
抽象的意思是抽取一类事物相同的属性进行抽象,比如每个表都有列(也可能不存在列,但那是特例)。但是每个表可能是不同的,比如有的表有3列,有的是2列,列数相同时列名又不同等等。
M中的抽象类型除非是某些特殊情况,通常它们的类型不会相等,即使它们的定义相同。
type nullable T
也是抽象类型是因为type T
或type null
都被type nullable T
兼容,没有值直接属于type nullable T
。
42
是number
类型,null
是null
类型,它们都不是nullable number
类型,只是兼容于其中。
标准库函数Type.IsNullable
和Type.NonNullable
可用于测试类型是否可以为null
和使类型不可以为null
。
对于任何类型type T
以下条件适用:
type T
与type nullable T
兼容Type.NonNullable(type T)
与type T
兼容
Type.Is(type text, type nullable text)
Type.Is(type nullable text, type text)
对于任何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
值的归属类型
值的归属类型是声明值符合的类型。当一个值被赋予一个类型时,只会进行有限的一致性检查。M在可为null的原始类型范围之外不进行一致性检查。M程序作者选择归属类型定义比可为null的原始类型更复杂的值时,必须确保这些值符合这些类型。
值可以使用Value.ReplaceType
为值归属类型。如果新类型与值的本机原始类型(native primitive type)不兼容,则函数返回具有已归属类型的新值,或引发一个错误。特别是当尝试归属抽象类型时,该函数将会引发错误。
Value.Type
可以获得值的归属类型。
类型等效性和兼容性
M中未定义类型等效性。比较等效性的任何两个类型值可能返回,也可能不返回true
。然而,这两种类型(不管是true
还是false
)之间的关系总是相同的。
可以使用库函数Type.Is
来检查右侧类型是否兼容左侧类型。
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中不支持指定类型和自定义类型的兼容性。
标准库中包含一个函数可以提取自定义类型的定义特征。
// type number
Type.ListItem( type {number} )
// type text
Type.NonNullable( type nullable text )
// [ A = [Type = type text, Optional = false],
// B = [Type = type time, Optional = false] ]
Type.RecordFields( type [A=text, B=time] )
// type [X = number, Y = date]
Type.TableRow( type table [X=number, Y=date] )
// [ x = type number, y = type nullable text ]
Type.FunctionParameters(
type function (x as number, optional y as text) as number)
// 1
Type.FunctionRequiredParameters(
type function (x as number, optional y as text) as number)
// type number
Type.FunctionReturn(
type function (x as number, optional y as text) as number)