你可以在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程式碼的最前面,也就是「主要」運算式之前。
_函式_可以巢狀:
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會進行尾呼叫最佳化,但只限於參數數量為 0 的函式。
在這道練習裡,你會操作一台遙控車,這是你終於存夠錢買下的喔。
車子一開始的電量是滿的(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"
# }