You can define your own custom functions in jq
to encapsulate whatever logic you need.
Functions act just like builtins: they take an input and emit zero, one or more outputs.
You can define a jq
function using the following syntax:
# no arguments
def funcname: expression;
# or with arguments
def funcname(args): expression;
def
keyword,jq
syntax, you can use arbitrary whitespace for readability.Functions must be defined before they are used: this is an error:
def A: B(10);
def B(n): n + 1;
A
# => error: B/1 is not defined
This implies you have to place functions at the top of your jq
code, prior to the "main" expression.
Functions can be nested:
def A:
def B(n): n + 1;
B(10)
;
A
# => 11
Here, the B
function is only visible in the body of A
.
A function introduces a new scope for variables and nested functions.
Function arguments are separated by semi-colons not commas. For example, a function that takes a number, and then adds a number and multiplies by a number:
def add_mul(adder; multiplier): (. + adder) * multiplier;
10 | add_mul(5; 4) # => 60
Semi-colons are needed because comma already has a purpose in jq
: an operator that joins streams.
Using a comma instead of a semi-colon will attempt to make two calls to a 1-argument add_mul
function, which doesn't exist and therefore will fail on the first attempted call:
10 | add_mul(5, 4)
# error: add_mul/1 is not defined
Function arguments are filters, not values. In this sense, they act like what other languages describe as callbacks:
Using the add_mul
function as an example:
10 | add_mul(. + 5; . - 2) # => 200
What's happening here?
adder
argument gets the expression . + 5
. + adder
, that becomes . + . + 5
. == 10
multiplier
argument is the expression . - 2
25 * 8 == 200
Sometimes you'll want to "materialize" an argument into a variable:
def my_func(arg):
arg as $arg
| other stuff ...
;
There's a shorthand for this:
def my_func($arg):
other stuff ...
;
Take note that this is just "syntactic sugar": the name arg
with no $
is still in scope in the function.
Functions have an arity -- the number of arguments they take.
Functions can use the same name with different arities.
The builtin range
function demonstrates this: range/1
, range/2
and range/3
all co-exist.
This can be useful for defining recursive functions that carry state via arguments.
For example map
could be implemented like:
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
will perform tailcall optimization, but for 0-arity functions only.
A jq
module is a file containing only functions.
Modules are included into a jq program with the include
or import
commands.
In this exercise you'll be playing around with a remote controlled car, which you've finally saved enough money for to buy.
Cars start with full (100%) batteries. Each time you drive the car using the remote control, it covers 20 meters and drains one percent of the battery. The car's nickname is not known until it is created.
The remote controlled car has a fancy LED display that shows two bits of information:
"<METERS> meters"
."Battery at <PERCENTAGE>%"
.If the battery is at 0%, you can't drive the car anymore and the battery display will show "Battery empty"
.
Implement the new_remote_control_car/0
function to return a brand-new remote controlled car object:
new_remote_control_car
# => {
# "battery_percentage": 100,
# "distance_driven_in_meters": 0,
# "nickname": null
# }
Implement the new_remote_control_car/1
function to return a brand-new remote controlled car object with a provided nickname:
new_remote_control_car("Blue")
# => {
# "battery_percentage": 100,
# "distance_driven_in_meters": 0,
# "nickname": "Blue"
# }
Implement the display_distance/0
function that takes a car object as input and outputs the distance string as displayed on the LED display:
new_remote_control_car | display_distance
# => "0 meters"
Implement the display_battery/0
function that takes a car object as input and outputs the battery percentage string as displayed on the LED display:
new_remote_control_car | display_battery
# => "Battery at 100%"
If the battery is at 0%, the battery display will show "Battery empty".
Implement the drive/0
function that:
new_remote_control_car("Red") | drive
# => {
# "battery_percentage": 99,
# "distance_driven_in_meters": 20,
# "nickname": "Red"
# }
Update the drive/0
function to not increase the distance driven nor decrease the battery percentage when the battery is drained (at 0%):
{
battery_percentage: 0,
distance_driven_in_meters: 2000,
nickname: "Red"
} | drive
# => {
# "battery_percentage": 0,
# "distance_driven_in_meters": 2000,
# "nickname": "Red"
# }
Sign up to Exercism to learn and master jq with 12 concepts, 74 exercises, and real human mentoring, all for free.