A q programozási nyelv

Alprogramok, modulok

Szintaxis

Egy függvény a q-ban megfelel egy matematikai leképezésnek, amit egy algoritmus definiál. Egy függvény kifejezések sorozata, opcionális bemenő paraméterekkel és egy visszatérési értékkel. A függvény törzsét kapcsos zárójelek közé írjuk. A függvény törzse kifejezések szekvenciájából áll, pontosvesszővel elválasztva. A függvény input paramétereinek és visszatérési értékének a típusát nem határozhatjuk meg.

Más nyelvekkel ellentétben a q-ban nincs "függvénydefiníció" mint nyelvi elem. Csak lambda kifejezés van, amelyet értékül adhatunk egy változónak. A változónevet ezután tekinthetjük a függvény "nevének".

A következő példa egy teljes specifikációja egy függvénynek, amely visszatér az inputja négyzetével. A whitespace a paraméter után opcionális:

f:{[x] x*x}
Az f függvény meghívása úgy történik, hogy a függvény neve után szögletes zárójelekbe írjuk annak paramétereit:
f[3] 9
Egyparaméteres függvénynél elhagyhatjuk a zárójeleket, de ekkor vigyázzunk a kifejezések kiértékelési sorrendjére.
f 3 + 4 49
A lista indexeléséhez hasonlóan lehetőség van paraméterek elhagyására is, ilyenkor részlegesen applikált függvényt kapunk. A függvény törzse csak akkor kerül végrehajtásra, ha már minden paraméterét megadtuk. (Nulla paraméterű függvénynél is használni kell a [] jeleket, vagy átadni egy tetszőleges "dummy" paramétert.)
g:{[x;y;z] x+y+z} g[1;;3][5] 9
A legfeljebb három paraméterű függvények esetén elhagyhatjuk a paraméterlistát. Ilyenkor az x, y és z azonosítókkal hivatkozhatunk a paraméterekre, a q automatikusan kitalálja, hány paraméteresnek kell lennie a függvénynek. A következő példa a négyzetre emelő függvény, amelyből elhagytuk az opcionális részeket:
{x*x}[5] 25
A példákból láthatjuk, hogy a függvénydefiníciók szintaxisa a következő:
{[p1;...;pn] e1; ...; en}
ahol p1, ..., pn opcionális paraméterek, e1, ..., en pedig kifejezések sorozata, melyek balról jobbra értékelődnek ki.

Egy többsoros függvénydefiníció így a következőképpen néz ki:

f:{[p1;...;pn] e1; ...; en}

A függvények rendelkezhetnek lokális változókkal. Ha a függvényben értéket adunk egy olyan változónak, amely még nem létezett, akkor az automatikusan lokális változóként jön létre:

f:{a:x+y+z; :a%3}

A lokális változót csak az adott függvényből érhetjük el, a függvény belsejében definiált más függvényekben már nem.

f:{a:1 2 3 4; {x+max a}[5]} /hiba f:{a:1 2 3 4; {[a;x] x+max a}[a;5]} /így már jó

A névtérnévvel ellátott globális változók a szokásos módon felhasználhatók és módosíthatók függvényekben is:

f:{.foo.bar:x} g:{:.foo.bar+x} f 6 .foo.bar 6 g 5 11

A névtérnév nélküli globális változók kezelése problémásabb. Olvasáskor mindig abban a névtérben keresi a változót, amely a függvény definiálásakor éppen aktív volt, nem pedig magának a függvénynek a névterében, sem a hívás névterében:

.foo.a:5 .bar.a:6 .baz.a:7 \d .bar .foo.g:{:a} \d .baz .foo.g[] 6 \d .foo g[] 6

Az értékadás nem működik az egyszerű ":" operátorral, mert az lokális változót hoz létre. Helyette a "::" operátort kell használnunk. Kivétel a lista, szótár stb. elemenkénti értékadása - ebben az esetben már működik a sima ":" is.

{a:5}[] 5 a 'a {a::5}[] a 5 b:1 2 3 {b[1]:10}[] b 1 10 3

Mindezek miatt a névtérnév nélküli globális változók használata a függvényekben kerülendő.

Paraméterátadás

Q-ban az összes paraméter (listák, táblák is) másolva kerül átadásra.

Túlterhelés

A függvények paramétereinek nem adhatunk meg típust, se nem konstansságot, de nem is ez a lényeg itt, mert ettől még paraméterszámban túlterhelhetnénk függvényt. Az az igazság, hogy q-ban minden függvény "névtelen". Egy változónak lehet egy függvényt értékül adni, ilyenkor annak a változónak a típusa függvény lesz. Ezt a változót aztán átadhatjuk más függvényeknek, tárolhatjuk listában, stb. Máshogy szólva q-ban igazából csak függvénypointerekről beszélhetünk, amelyek névtelen függvényekre mutatnak. Ezért aztán amikor ugyanolyan névvel próbálunk definiálni egy másik függvényt, akkor igazából egy változó értékadás történik, tehát a függvénypointerünk egy új függvényre fog mutatni. Ez a viselkedés kizárja a túlterhelés lehetőségét.

Rekurzió

A q nyelv erősen támogatja a funkcionális programozást, így természetesen lehetőség van benne rekurzióra. Itt azonban vigyáznunk kell, mivel minden függvény névtelen.

fact:{$[x;x*fact x-1;1]}

Első ránézésre ez egy jó rekurzív függvénynek tűnik, de problémák merülhetnek fel vele:

perm:fact perm {$[x;x*fact x-1;1]}

A függvény értékadás után még mindig a régi névre hivatkozik, ami hibaforrás lehet. Ezért a q-ban létezik egy speciális azonosító, a .z.s ("self"). Ez mindig az éppen végrehajtás alatt álló függvényt tartalmazza, segítségével megoldható a névtelen rekurzív függvény írása.

fact:{$[x;x*.z.s x-1;1]}

Infix függvények

Általában ha egy függvényt definiálunk, azaz értékül adjuk egy változónak, a változónév a q szintaxisa szerint főnév lesz. Ezért csak a jobb oldalán állhat paraméter.

pow:{prd y#x} / x összeszorzása y-szor pow[2;8] 256 (pow 2) 8 256 2 pow 8 'type

Hiába mutatna jól, hogy a "pow" nevet mint operátort a két argumentum közé írjuk, a szintaxis ezt nem engedi meg. Két kivétellel: az egyik lehetőség, hogy egy módosítószóval együtt használjuk a függvény nevét, ezáltal automatikusan infix operátor lesz belőle. A másik abból a megfigyelésből adódik, hogy a q-ban van néhány beépített függvény, amely lambda kifejezéssel van megadva, de mégis infix módon használható:

", " sv ("alfa"; "beta"; "gamma") "alfa, beta, gamma" sv k){x/:y}

Mégis, ha értékül adjuk egy saját változónak az "sv" értékét, a kapott változó nem lesz infix módon használható. A titok nyitja az, hogy a beépített függvények a .q névtérben vannak definiálva. Ez a névtér két speciális tulajdonsággal is bír: a benne levő függvények nevében egyrészt nem kell kiírni a .q névtérnevet, másrészt kétparaméteres függvény esetén ilyenkor infix módon használhatóak. Ha a saját függvényünket a .q névtérbe rakjuk, akkor az is rendelkezni fog ezekkel a tulajdonságokkal:

.q.pow:{prd y#x} / x összeszorzása y-szor 2 pow 8 256
Ez azzal is jár, hogy az egyparaméteres applikációnál a függvénynév és a paraméter sorrendje megcserélődik, pl. (2 pow) helyes részleges applikáció, míg (pow 2) már hiba. Másrészt viszont a q fejlesztői fenntartják maguknak a .q és az összes egyéb egybetűs névteret speciális használatra, így semmi se garantálja, hogy a következő q verzióban nem lesz olyan nevű függvény, amelyet éppen be szeretnénk tenni a .q névtérbe, szóval nem ajánlott ennek a technikának a használata.

Atomi műveletek és módosítószavak

A q-ban a legtöbb beépített operátor rendelkezik az atomi tulajdonsággal. Ez azt jelenti, hogy a művelet mindig csak atomok között kerül végrehajtásra. Ha nem atomi paramétert adunk nekik, akkor automatikusan kiterjesztésre kerülnek az alábbi módon.

Ha egy atomi műveletnek egy atomot és egy listát adunk át, akkor a művelet a lista egyes elemei és az atom között kerül végrehajtásra:

1 2 3 4+100 101 102 103 104 100-1 2 3 4 99 98 97 96

Ha két listát adunk át, akkor páronként hajtódik végre a művelet. Ebben az esetben a listáknak azonos hosszúaknak kell lenniük:

1 2 3 4+ 10 30 20 40 11 32 23 44 100 101-1 2 3 4 'length

Ez a kiterjesztés rekurzív, ezért működik mátrixokra, szótárakra és táblákra is.

Saját függvény esetén a kiterjesztés nem működik automatikusan, mivel a paramétereket nem lehet megjelölni, hogy elfogadhatnak-e listát. Ezért a lista egy az egyben átadódik a paraméterben. A függvényben természetesen továbbadhatjuk a listát egy atomi műveletnek, így az kiterjesztésre kerül. A hívás helyén is lehetőség van az alapértelmezett viselkedés felülbírására a módosítószavak (adverb) segítségével. A q 6 beépített módosítószót tartalmaz, újakat nem tudunk megadni. Ezeket a szimbólumokat "tapadószóként" a módosítandó függvény mögé írjuk, szóköz nélkül. Az így keletkezett függvény infix operátor lesz, ha még nem volt az.