Ми можемо визначати власні функції у 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 виконує оптимізацію хвостових викликів, але лише для функцій з арністю 0.
Модуль jq - це файл, що містить лише функції.
Модулі підключаються до програми jq командами include або import.
У цій вправі ми пограємося з радіокерованою машинкою, на яку нарешті назбирали достатньо грошей.
Машинки починають роботу з повністю зарядженими (100%) акумуляторами. Щоразу, коли ми керуємо машинкою за допомогою пульта, вона проїжджає 20 метрів і витрачає один відсоток заряду акумулятора. Прізвисько машинки невідоме, доки її не створено.
Радіокерована машинка має вигадливий 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, яка приймає обʼєкт машинки як вхідні дані та повертає рядок тексту (англ. string) з відстанню, як на 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"
# }
Зареєструйтеся на Exercism, щоб вивчати й опановувати jq, а також 12 концепцій75 вправ та справжнє наставництво від людей, і все це безкоштовно.