میتوانید توابع سفارشی خودتان را در 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 است، حاصل ۲۵ میشودmultiplier عبارت . - 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 بهینهسازی فراخوانی انتهایی را انجام میدهد، اما فقط برای توابع با تعداد آرگومان صفر.
ماژول jq فایلی است که فقط شامل توابع میشود.
ماژولها با فرمانهای include یا import به یک برنامهی jq وارد میشوند.
در این تمرین با یک ماشین کنترلی سر و کار دارید؛ ماشینی که بالاخره پول کافی برای خریدنش را پسانداز کردهاید.
ماشینها با باتری کاملاً پر (۱۰۰٪) شروع میکنند. هر بار که ماشین را با کنترل از راه دور میرانید، ۲۰ متر جلو میرود و یک درصد از باتری را مصرف میکند. اسم مستعار ماشین تا زمانی که ساخته نشود، معلوم نیست.
ماشین کنترلی یک نمایشگر LED شیک دارد که دو بخش اطلاعات را نشان میدهد:
"<METERS> meters"."Battery at <PERCENTAGE>%".اگر باتری به ۰٪ برسد، دیگر نمیتوانید ماشین را برانید و نمایشگر باتری "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%"
اگر باتری به ۰٪ برسد، نمایشگر باتری «Battery empty» را نشان میدهد.
تابع drive/0 را پیادهسازی کنید که:
new_remote_control_car("Red") | drive
# => {
# "battery_percentage": 99,
# "distance_driven_in_meters": 20,
# "nickname": "Red"
# }
تابع drive/0 را طوری بهروزرسانی کنید که وقتی باتری خالی است (۰٪)، نه مسافت طیشده افزایش یابد و نه درصد باتری کاهش پیدا کند:
{
battery_percentage: 0,
distance_driven_in_meters: 2000,
nickname: "Red"
} | drive
# => {
# "battery_percentage": 0,
# "distance_driven_in_meters": 2000,
# "nickname": "Red"
# }