each表达式详解
each表达式详解
温故而知新
在了解关于each
的全部内容前,你应该了解关于函数的定义。由于本文不是讲解函数的内容,因此此处仅简单概述函数的定义。
常见的函数定义为:
// 不带类型的定义
(a, b) => a +b
// 不带类型且b为可选参数
(a, optional b) => a + (b ?? 0)
// 限制参数为数字且输出文本的函数
(a as number) as text => Text.From(a)
语法定义
each
语法定义:
each-expression:
each
each-expression-body
each-expression-body:
function-body
简单的说,each
表达式的形式为:each 函数主体
。each
表达式返回一个函数,该函数仅拥有一个名为_
的参数,且参数和返回值的类型可以是任何类型。实际上,each
是(_)=>
的语法替代,即可以在源码上直接替换。具体演进过程如下:
// 普通函数
(x) => x + 1
// 将x替换为_
(_) => _ + 1
// 将(_) =>替换为each
each _ + 1
提示
each
返回的是函数,所以你可以像使用普通函数一样将其赋值给变量,比如:fx = each _ + 1
。
隐式目标访问
通常我们访问记录中的字段时,会使用类似下面的语法:
let
r = [a = 1, b = 2]
in
r[a]
上面是最常见的语法,它使用主表达式[字段名]
的方式对记录字段进行访问。但你也见过下面的写法:
Table.AddColumn(
tbl,
"新列名",
each [A列] + 1
)
你应该注意到Table.AddColumn
中[A列]
的前面并没有任何东西,如果[A列]
前面没有主表达式
,那么它会使用环境中的_
作为主表达式
,官方将其称之为:隐式目标字段访问。因此,下面的写法是合法的且符合预期的:
let
_ = [a = 1, b = 2]
in
[a]
由于each
表达式会定义名为_
的参数,因此这种隐式访问会经常在each
表达式中见到。具体的演进过程:
// 普通访问A列
(r) => r[A列] + 1
// 将r替换为_
(_) => _[A列] + 1
// 将(_) =>替换为each
each _[A列] + 1
// 隐去_
each [A列] + 1
each嵌套
虽然文本经常提到each
可以替换为(_)=>
,但实际操作时会有很多问题需要注意,尤其是嵌套时:
let
源 = Table.FromValue({1..5}),
演示 = Table.AddColumn(
源,
"新列",
each List.Generate(
() => 1,
each _ <= [Value],
each _ + 1
)
)
in
演示
上面的例子先构建了一个只有一列的表,列名为Value
。然后添加一个新的列,新列的每行都是一个1到对应数字的列表,比如第三行会得到1、2、3组成的列表。但上面的查询是错误的,因为List.Generate
二参中[Value]
会隐式访问环境中的_
,但此时有效的_
来自于List.Generate
二参,而非表行。修正这个错误的方式有两种。
第一种是修改List.Generate
二参,只要在二参中不使用_
作为参数名,那么[Value]
就会跳出List.Generate
寻找,进而访问到表行:
let
源 = Table.FromValue({1..5}),
演示 = Table.AddColumn(
源,
"新列",
each List.Generate(
() => 1,
(x) => x <= [Value],
each _ + 1
)
)
in
演示
提示
就近原则:当环境中存在多个相同名称(不同层级)的变量(或字段名)时,M引擎会先在当前层级查找,如果没有就会逐层向外查找,直到根环境。
第二种方法是修改List.Generate
前面的each
和二参中的[Value]
:
let
源 = Table.FromValue({1..5}),
演示 = Table.AddColumn(
源,
"新列",
(x) => List.Generate(
() => 1,
each _ <= x[Value],
each _ + 1
)
)
in
演示
如果你可以看懂上面的例子,那么恭喜你已经完全掌握了each
。为了进一步巩固,请继续查看下面的例子:
let
源 = Table.FromValue({1..5}),
演示 = Table.AddColumn(
源,
"新列",
each List.Transform({1..[Value]}, each Text.From(_))
)
in
演示
上面的例子中,还是创建一个仅有单列的表,列名为默认的Value
,然后新建一个列,还是生成一个1到当前行数字的列表,但我们还需要将这个列表中的每个元素转为文本。
首先,上面的查询是没有问题的,但需要优化。因为,each
表达式返回一个函数且它仅有一个参数。同时,Text.From
也是一个函数且它仅需要传入一个参数(实际上它能传入两个参数,但此时仅需要且可以只传入一个),因此我们可以将上面的查询优化为:
let
源 = Table.FromValue({1..5}),
演示 = Table.AddColumn(
源,
"新列",
each List.Transform({1..[Value]}, Text.From)
)
in
演示