函数是 Power Query (PQ) 解决问题的核心。之前的博客文章我多层刻意在完成查询的步骤之后显示高级编辑器中 M 语言代码,想必大家已经对 M 的函数有了初步印象,尽管不一定很关注。本篇介绍 M 函数比较重要的知识点。
要点:
- 理解 M 语言的函数式编程风格
- 函数的定义和调用
- 函数作为函数参数传递
- 自定义函数基础
学习 PQ 的处理数据,尽管我们大部分是在查询编辑器图形化界面中操作,但幕后将我们操作步骤记录下来的,都是 M 语言的代码。M 语言的代码,主要由函数构成。M 语言的函数与 Excel 函数不同,与其他常规编程语言的函数也不同,属于典型的函数式编程风格。为了更好的掌握 M 代码的编写,建议用自己熟悉的编程语言 (比如 Python)多去了解函数式编程的特点。
我们先解释一下 M 函数的基本语法格式。M 函数采用的是一种 F# 类似的语法。举一个例子,假如我们要编一个函数,实现两个数字相加,这个技术上都没有任何难度,示例主要目的就是为了熟悉 M 语言函数的语法。
函数的定义
进入查询编辑器,新建一个空查询,将查询名改为 add
,进入高级编辑器,在高级编辑器中输入下面的一行代码:
(x, y) => x + y
这就是 M 语言的函数,箭头符号 (=>)左边是参数列表,箭头符号右边是函数体。点击完成按钮回到查询编辑器,此时界面如下:
有几个值得我们注意的地方。第一:左边查询列表中 add 的符号变成 fx,表示这是一个函数,与之前介绍的查询概念不同。PQ 的函数可以供其他查询或者函数调用,得到一个返回值,从而对功能进行一个封装,增加代码的灵活性。
第二:这种独立编写的函数(top level function)在当前工作簿中,可以被其他查询或者函数调用。
第三:PQ 提供了图形化界面,可以在界面中输入参数值进行测试。标准库的函数也可以用这种方式来运行和测试结果。
点击调用按钮,PQ 自动创建一个新的名为“调用的函数”的查询,查询编辑器的界面显示了调用的结果:
参数的数据类型
选中 add 函数,再次进入高级编辑器,将代码修改为:
(x as number, y as number) as number => (x + y)
点击完成按钮回到查询编辑器界面,比较一下与之前界面的区别:
用这种方式,限定了参数和函数返回值的类型。
函数也可以与 let 语句一起使用,比如这个简单的 add 函数,将代码修改为如下:
let
result = (x, y) => (x + y)
in
result
代码的功能与前面相同。这段代码可以理解为:定义一个两数字相加的函数,将函数赋值给 result,result 作为结果输出到函数 add。
如果函数比较简单,又是临时用一下,编写一个 top level function 就显得比较浪费,这种情况下,可以在查询步骤中定义函数。为了区分,可以将嵌入在查询步骤中的函数称为内联函数 (inline function)。
所以记住 M 语言可以在两个位置定义函数:
- top level function
- inline function
新建一个空查询,在高级编辑器中编写如下代码:
let
add = (x, y) => (x + y),
a = 10,
b = 20,
result = add(a, b)
in
result
这段代码定义了一个名为 add 的函数,并且调用函数对 a 和 b 两个变量相加,计算的结果通过 result 进行输出。
函数的调用
Power Query 标准库中一共有 700 多个函数,有些函数非常简单,有些则非常复杂。我们先看一个简单的函数:
Text.Proper(text as nullable text, optional culture as nullable text) as nullable text
依据 Microsoft Docs: Text.Proper:
Returns the result of capitalizing only the first letter of each word in text value text. All other letters are returned in lowercase.
将文本的首字母变成大写,其他字母全部小写。我们直接在查询编辑器中调用该函数:
这是简单函数的调用,看不出与其他语言的函数有什么区别,对吧?演示一个稍微复杂一点的函数 List.Select
,我们先看看 Microsoft Docs - List.Select 的语法和解释:
List.Select(list as list, selection as function) as list
根据筛选条件,返回 list 中匹配的元素作为一个新的 list,筛选条件必须是一个函数。
函数能作为参数进行传递,是函数式编程的一大特点。考虑到 Power Query 主要面向数据处理人员,这里不对函数式编程进行展开说明,但需要了解这一特点。函数作为参数的函数,如何调用呢?我们先给出调用示例,在高级编辑器中输入下面的代码:
// 输出 1 到 30 所有能被 3 整除的整数
let
source = {1..30},
result = List.Select(source, (x) => Number.Mod(x, 3) = 0)
in
result
对代码解释一下:
List.Select
函数规定第二个参数是一个函数,用于表示筛选条件。
(x) => Number.Mod(x, 3) = 0
这个函数定义了一个筛选条件,当 x 能被 3 整除,则返回 true,为了表示能被 3 整除这个条件,函数又调用了另外一个 Number.Mod
函数,用于判断是否能整除。
当代码中调用 List.Select
函数的时候,List.Select
函数 自动遍历 list 中的每一个元素,并且将元素赋值给函数第二个参数(即函数 (x) => Number.Mod(x, 3) = 0
)中的参数 x,然后调用这个匿名函数,当返回 true 时,这个元素就被判定为匹配,加入到返回值的 list 中。因为 List.Select
具有自动遍历的功能,所以我们也可以理解为,List.Select
的第二个参数必须是函数,这个函数含有一个名字不重要的变量 (x),对这个变量 (x) 运行一个函数后,能计算得到 true 或者 false。对刚才的查询稍微改一下,以加深大家的理解:
// Get numbers greate than 20
let
source = {1..30},
result = List.Select(source, (current) => current >= 20 )
in
result
Power Query 的 each 究竟是什么
前面我们在 PQ 中处理数据的时候,经常看到有 each,因为没有介绍函数,也就一直没有解释。这个 each 究竟是什么呢?each 定义了一个接收唯一参数的函数,是对 (_) => expression
的简写。下划线 (_
)在 M 语言中,代表函数无类型唯一的参数。根据这一解释,上面的代码可以简化为:
let
source = {1..30},
result = List.Select(source, (_) => _ >= 20 ) // parameter name
in
result
或者:
let
source = {1..30},
result = List.Select(source, each _ >= 20 )
in
result
函数是 Power Query 的核心,也是难点。为降低学习难度,先介绍这些基础,后续再逐步深化。
References
- Power Query M function reference