类型
类型
类型是对值的分类,类型值是保存值类型的值。
原生类型(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:
optionalopt 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)