使用英语阅读

通过


类型

类型值是一个对其他值进行分类的值 。 认为按类型分类的值符合该类型。 M 类型系统由以下类型组成:

  • 基元类型可以对基元值(binarydatedatetimedatetimezonedurationlistlogicalnullnumberrecordtexttimetype)进行分类,并且基元类型还包括许多抽象类型(functiontableanyanynonnullnone

  • 记录类型(根据字段名称和值类型对记录值进行分类)

  • 列表类型(使用单一项基类型对列表进行分类)

  • 函数类型(根据其参数和返回值类型对函数值进行分类)

  • 表类型(根据列名、列类型和键对表值进行分类)

  • 可为 null 的类型(不仅可以按基类型对所有值进行分类,还可以对 null 值进行分类)

  • 类型类型(对属于类型的值进行分类)

一组基元类型包含基元值的类型和许多抽象类型,这些类型不对任何值进行唯一分类:functiontableanyanynonnullnone。 所有函数值都符合抽象类型 function,所有表值都符合抽象类型 table,所有值都符合抽象类型 any,所有非 null 值都符合抽象类型 anynonnull,没有值符合抽象类型 none。 类型为 none 的表达式必须引发错误或无法终止,因为无法生成符合类型 none 的值。 请注意,基元类型 functiontable 是抽象的,因为任何函数或表都不直接属于这些类型。 基元类型 recordlist 是非抽象的,因为它们分别表示一个没有定义字段的 open 记录和一个 any 类型的列表。

所有不属于封闭基元类型集成员的类型及其可为空对应对象的类型统称为自定义类型。 可以使用 type-expression 编写自定义类型:

type-expression:
      primary-expression

      typeprimary-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 }

同样,以下示例定义了一个自定义类型,该类型使用名为 XY 的必需字段(其值为数字)对记录进行分类:

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

请注意,isas 运算符仅接受可为空的基元类型作为其正确的操作数。 M 不提供用于检查值是否符合自定义类型的方法。

当且仅当符合 X 的所有值也符合 Y 时,类型 X 与类型 Y兼容。 所有类型都与类型 any 兼容,没有类型(除了 none 本身)与类型 none 兼容。 下图显示了兼容性关系。 (类型总是与其自身兼容,且类型兼容性可传递。它形成了一个点阵,类型 any 在最上,类型 none 在最下。)抽象类型的名称设置为斜体。

类型兼容性

为类型值定义了以下运算符:

运算符 结果
x = y 等于
x <> y 不等于
x ?? y Coalesce

类型值的本机类型是内部类型 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:
      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:

      optionalopt field-name field-type-specificationopt
field-type-specification:

      =field-type
field-type:
      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:

      optionalparameter-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 类型与形式参数的类型兼容,并且如果形式参数是可选的,则参数规范与形式参数兼容。

为了确定函数类型符合性,会忽略形式参数名称。

将参数隐式指定为可选参数可使其类型为 null。 以下内容会创建相同的函数类型:

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

表类型

使用 table-type 值来定义表值的结构。

table-type:
      tablerow-type
row-type:

      [ field-specification-listopt ]

计算 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.TableKeysType.AddTableKeyType.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类型

结果是一个抽象类型,允许类型 T 的值或值 null

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

T 的 Ascription type nullable type null type 可减少为或 T 的描述。(回想一下,可为 null 的类型是抽象的,不能直接具有抽象类型的值。

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

标准库函数 Type.IsNullableType.NonNullable 可用于测试类型是否可为 null,并使类型不可为 null。

以下条件适用(对于任何 type T):

  • type Ttype nullable T 兼容
  • Type.NonNullable(type T)type T 兼容

以下是成对等效(对于任何 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

值的归属类型

值的归属类型声明一个值是否符合的类型。

使用库函数 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) 
  // 1 
Type.FunctionReturn(
        type function (x as number, optional y as text) as number) 
  // type number