你可以在 jq 中定义自己的自定义函数,把需要的逻辑封装起来。
_函数_的行为和内置函数一样:接收一个输入,产生零个、一个或多个输出。
你可以用下面的语法来定义 jq 的_函数_:
# no arguments
def funcname: expression;
# or with arguments
def funcname(args): expression;
def 关键字开头,jq 的其他语法一样,你可以用任意空白字符来提升可读性。_函数_必须先定义再使用,下面这样写会报错:
def A: B(10);
def B(n): n + 1;
A
# => error: B/1 is not defined
这意味着你必须把_函数_放在 jq 代码的顶部,也就是“main”表达式的前面。
_函数_可以嵌套:
def A:
def B(n): n + 1;
B(10)
;
A
# => 11
这里的_函数_ B 只在 A 的函数体内可见。
一个_函数_会为变量和嵌套函数引入一个新的作用域。
_函数_的实参用_分号_分隔,而不是逗号。 例如,下面这个_函数_接收一个数字,然后把它加上一个数,再乘以一个数:
def add_mul(adder; multiplier): (. + adder) * multiplier;
10 | add_mul(5; 4) # => 60
之所以需要分号,是因为逗号在 jq 中已经有用途了:它是一个用来连接流的运算符。
用逗号代替分号,会试图对_只有一个实参_的 add_mul 函数发起两次调用,但这种函数并不存在,所以第一次调用就会失败:
10 | add_mul(5, 4)
# error: add_mul/1 is not defined
函数的_实参_是过滤器,而不是值。 从这个意义上说,它们的行为就像其他语言里的回调:
以 add_mul 函数为例:
10 | add_mul(. + 5; . - 2) # => 200
这里发生了什么?
adder 实参拿到的是_表达式_ . + 5
. + adder 时,它就变成了 . + . + 5
. == 10,结果为 25multiplier 实参是表达式 . - 2
25 * 8 == 200
有时你会想把一个_实参_“固化”成变量:
def my_func(arg):
arg as $arg
| other stuff ...
;
这一点有个简写形式:
def my_func($arg):
other stuff ...
;
注意,这只是“语法糖”:不带 $ 的名字 arg 在该_函数_中依然在作用域内。
_函数_有一个元数,也就是它接收的_实参_个数。
函数_可以用同一个名字定义不同的_元数。
内置的 range 函数就说明了这一点:range/1、range/2 和 range/3 可以同时存在。
这对于定义通过实参携带状态的递归函数会很有用。
例如 map _可以_这样实现:
def my_map($accumulator; func):
if length == 0
then $accumulator
else first as $elem | .[1:] | my_map($accumulator + [$elem | func]; func)
end
;
def my_map(func):
my_map([]; func)
;
[1, 2, 3, 4] | my_map(. * 10) # => [10, 20, 30, 40]
jq 会进行尾调用优化,但仅限零元数的函数。
在这个练习里,你会玩一辆遥控车,你终于攒够钱把它买下来了。
汽车一开始的电池是满的(100%)。 每次你用遥控器驾驶这辆车,它都会行驶 20 米,并消耗 1% 的电量。 这辆车的昵称只有在创建时才会确定。
这辆遥控车有一个很酷的 LED 显示屏,可以显示两条信息:
"<METERS> meters"。"Battery at <PERCENTAGE>%"。如果电量降到 0%,你就不能再驾驶这辆车了,显示屏会显示 "Battery empty"。
实现 new_remote_control_car/0 函数,返回一个全新的遥控车对象:
new_remote_control_car
# => {
# "battery_percentage": 100,
# "distance_driven_in_meters": 0,
# "nickname": null
# }
实现 new_remote_control_car/1 函数,返回一个带有指定昵称的全新遥控车对象:
new_remote_control_car("Blue")
# => {
# "battery_percentage": 100,
# "distance_driven_in_meters": 0,
# "nickname": "Blue"
# }
实现 display_distance/0 函数,它接收一个汽车对象作为输入,并输出 LED 显示屏上显示的距离字符串:
new_remote_control_car | display_distance
# => "0 meters"
实现 display_battery/0 函数,它接收一个汽车对象作为输入,并输出 LED 显示屏上显示的电量百分比字符串:
new_remote_control_car | display_battery
# => "Battery at 100%"
如果电量降到 0%,显示屏会显示 "Battery empty"。
实现 drive/0 函数,它会:
new_remote_control_car("Red") | drive
# => {
# "battery_percentage": 99,
# "distance_driven_in_meters": 20,
# "nickname": "Red"
# }
更新 drive/0 函数,使其在电量耗尽(为 0%)时既不增加行驶距离,也不减少电量:
{
battery_percentage: 0,
distance_driven_in_meters: 2000,
nickname: "Red"
} | drive
# => {
# "battery_percentage": 0,
# "distance_driven_in_meters": 2000,
# "nickname": "Red"
# }