Puoi definire le tue funzioni personalizzate in jq per incapsulare la logica di cui hai bisogno.
Le funzioni si comportano esattamente come i builtin: ricevono un input ed emettono zero, uno o più output.
Puoi definire una funzione jq con la seguente sintassi:
# no arguments
def funcname: expression;
# or with arguments
def funcname(args): expression;
def,jq, puoi usare spazi bianchi arbitrari per migliorare la leggibilità.Le funzioni devono essere definite prima di essere usate: questo è un errore:
def A: B(10);
def B(n): n + 1;
A
# => error: B/1 is not defined
Di conseguenza, devi collocare le funzioni all'inizio del tuo codice jq, prima dell'espressione «principale».
Le funzioni possono essere annidate:
def A:
def B(n): n + 1;
B(10)
;
A
# => 11
Qui, la funzione B è visibile solo nel corpo di A.
Una funzione introduce un nuovo scope per le variabili e le funzioni annidate.
Gli argomenti di una funzione sono separati da punti e virgola, non da virgole. Per esempio, una funzione che riceve un numero, poi aggiunge un numero e moltiplica per un numero:
def add_mul(adder; multiplier): (. + adder) * multiplier;
10 | add_mul(5; 4) # => 60
I punti e virgola sono necessari perché la virgola ha già uno scopo in jq: è un operatore che unisce gli stream.
Usare una virgola al posto di un punto e virgola tenta di fare due chiamate a una funzione add_mul a un argomento, che non esiste e quindi fallirà al primo tentativo di chiamata:
10 | add_mul(5, 4)
# error: add_mul/1 is not defined
Gli argomenti di una funzione sono filtri, non valori. In questo senso, si comportano come quello che altri linguaggi chiamano callback:
Prendiamo come esempio la funzione add_mul:
10 | add_mul(. + 5; . - 2) # => 200
Cosa succede qui?
adder riceve l'espressione . + 5
. + adder, questo diventa . + . + 5
. == 10
multiplier è l'espressione . - 2
25 * 8 == 200
A volte ti servirà «materializzare» un argomento in una variabile:
def my_func(arg):
arg as $arg
| other stuff ...
;
Esiste una sintassi abbreviata per farlo:
def my_func($arg):
other stuff ...
;
Tieni presente che si tratta solo di «zucchero sintattico»: il nome arg senza $ è comunque nello scope della funzione.
Le funzioni hanno un'arietà: il numero di argomenti che accettano.
Le funzioni possono usare lo stesso nome con arietà diverse.
La funzione builtin range lo dimostra: range/1, range/2 e range/3 coesistono tutte.
Questo può essere utile per definire funzioni ricorsive che trasportano stato tramite argomenti.
Per esempio, map potrebbe essere implementata così:
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 esegue l'ottimizzazione delle chiamate in coda, ma solo per le funzioni con arietà 0.
Un modulo jq è un file che contiene solo funzioni.
I moduli si includono in un programma jq con i comandi include o import.
In questo esercizio giocherai con un'auto radiocomandata, che finalmente hai risparmiato abbastanza soldi per comprare.
Le auto partono con la batteria completamente carica (100%). Ogni volta che guidi l'auto con il radiocomando, percorre 20 metri e consuma l'uno percento della batteria. Il soprannome dell'auto non è noto finché non viene creata.
L'auto radiocomandata ha un elegante display a LED che mostra due informazioni:
"<METERS> meters"."Battery at <PERCENTAGE>%".Se la batteria è allo 0%, non puoi più guidare l'auto e il display della batteria mostrerà "Battery empty".
Implementa la funzione new_remote_control_car/0 in modo che restituisca un oggetto auto radiocomandata nuovo di zecca:
new_remote_control_car
# => {
# "battery_percentage": 100,
# "distance_driven_in_meters": 0,
# "nickname": null
# }
Implementa la funzione new_remote_control_car/1 in modo che restituisca un oggetto auto radiocomandata nuovo di zecca con un soprannome fornito:
new_remote_control_car("Blue")
# => {
# "battery_percentage": 100,
# "distance_driven_in_meters": 0,
# "nickname": "Blue"
# }
Implementa la funzione display_distance/0 che prende in input un oggetto auto e restituisce la stringa della distanza così come viene visualizzata sul display a LED:
new_remote_control_car | display_distance
# => "0 meters"
Implementa la funzione display_battery/0 che prende in input un oggetto auto e restituisce la stringa della percentuale della batteria così come viene visualizzata sul display a LED:
new_remote_control_car | display_battery
# => "Battery at 100%"
Se la batteria è allo 0%, il display della batteria mostrerà "Battery empty".
Implementa la funzione drive/0 che:
new_remote_control_car("Red") | drive
# => {
# "battery_percentage": 99,
# "distance_driven_in_meters": 20,
# "nickname": "Red"
# }
Aggiorna la funzione drive/0 in modo che non aumenti la distanza percorsa né diminuisca la percentuale della batteria quando la batteria è scarica (allo 0%):
{
battery_percentage: 0,
distance_driven_in_meters: 2000,
nickname: "Red"
} | drive
# => {
# "battery_percentage": 0,
# "distance_driven_in_meters": 2000,
# "nickname": "Red"
# }
Iscriviti a Exercism per imparare e padroneggiare jq con 12 concetti75 esercizi e il mentoring di persone reali, tutto gratis.