A Perl programozási nyelvet szöveges fájlok adatainak összegyűjtésére és feldolgozására hozták létre. A nyelv még mindig erős szövegfeldolgozásban, de a Perl 5 már általános célú programozási nyelv is. A Perl 6 pedig még ennél is jobb.
Tegyük fel, hogy egy asztalitenisz bajnokságot rendezünk. A játékvezető megadja az összes meccs eredményét a következő formátumban: Player 1 vs Player 2 | 3:2. Egy olyan script kell nekünk, amely összegzi minden játékosra, hogy hány meccset és hány szettet nyert, és ebből megállapítja, hogy ki az abszolút nyertes.
Az input adatok:
Az első sorban a játékosok nevei, minden további sorban egy-egy meccs eredménye található.
A feladat egy megoldása Perl 6 nyelven:
Ez a következő outputot adja:
Minden Perl 6 programnak a use v6; sorral kell kezdődnie. Ez a sor mondja meg a fordítónak, hogy a Perl melyik verziójában van írva a program. Ha véletlenül Perl 5-el akarjuk futtatni a programot, akkor egy tájékoztató hibaüzenetet kapunk.
Egy Perl 6 program nulla vagy több utasításból áll. Minden utasítás ;-vel vagy }-lel végződik.
A my kulcsszóval deklarálhatunk lexikális változókat. Ezek csak az aktuális blokkban láthatóak. Ha nincsen blokk, akkor a változó a program egészén keresztül, egészen a végéig látható. Egy blokk a program azon része, amely { és } között szerepel.
Minden változó neve egy jellel kezdődik, ami a következő lehet: $, @, % vagy &, esetenként ::. Ez a jel általában megköti a változó típusát, úgy mint egyszerű vagy összetett típus. Ezután a jel után következik az azonosító, ami betűkből, számokból és _ jelből állhat, de betűk között használhatunk még - és ' jeleket is. (Így isn't és double-click elfogadható azonosítók.)
A $ jel egy skaláris változót jelez, ami azt jelenti, hogy a változóban egyszerű értéket tárolunk.
Az open beépített függvény megnyit egy fájlt, aminek a példában scores a neve és egy fájl kezelőt ad vissza, ami egy a megnyitott fájlra mutató objektum. Az egyenlőség jellel értékül adjuk ezt a fájl kezelőt a bal oldalon lévő változónak.
Az open függvény paramétere egy string literál: 'scores'. Perl 6-ban ' jelek között adhatunk meg szöveges adatot.
A jobb oldal meghívja a get függvényt a $file változóban tárolt fájl kezelőre. Ez a függvény kiolvassa a fájl első sorát és visszatér azzal, eltávolítva a sor vége jelet. A split is egy függvény, amelyet arra a stringre hívunk meg, amellyel a get függvény visszatért. Ez a függvény feldarabolja a stringet a paraméterként megadott karakter mentén, és stringek listáját adja vissza. Így a példánkban a 'Beth Ana Charlie Dave' stringet a következő 'Beth', 'Ana', 'Charlie', 'Dave' stringek listájára bontja.
Végül ez a lista a @names tömbben tárolódik el. A @ jel azt jelenti, hogy a változó egy tömb.
Ebben a két sorban két hash-t deklarálunk. A % jel mindegyik változó típusát hash-nek határozza meg. Ez rendezetlen párok gyűjteménye, ahol a párok közül az egyik a kulcs, a másik pedig az érték. Más programozási nyelvekben nevezik még ezt a típust hash táblának, szótárnak vagy map-nek. A %hash{$key} kifejezéssel lekérdezhetjük a hash táblából a $key kulcshoz tartozó értéket.
A példaprogramunkban %matches tartalmazza a játékosok nyert meccseinek számát, %sets pedig a játékosok által megnyert set-ek számát.
Változók deklarálásakor a jelek meghatározzák a változók alapértelmezett elérését is. A @ jellel jelölt változók pozíciójukkal, míg a % jellel jelöltek kulcs alapján érhetők el. A $ jellel jelölt változó azonban egy általános tárolót jelent, ami bármit tartalmazhat.
A for egy ciklust állít elő, amely a { és } közötti blokkban lévő utasításokat hajtja végre a lista minden elemére egyszer. A $line változó minden iterációban az aktuális értéket tartalmazza. A $file.lines utasítás eredménye sorok listája, amiket a scores fájlból olvas ki, attól kezdve, ahol a $file.get utasítás abbahagyta, egészen a fájl végéig.
Az első iteráció során a $line változó az 'Ana vs Dave | 3:0' stringet tartalmazza, a második során a 'Charlie vs Beth | 3:1' stringet és így tovább.
A my kulcsszóval többszörös deklarációra is van lehetőség. Ez az utasítás a | jel mentén szétvágja a stringet és a $pairing és $result változókba teszi az eredményt. Az első sorra végrehajtva ezt az utasítást a $pairing változó az 'Ana vs Dave' stringet tartalmazza, a $result változó pedig a '3:0'-t. A következő két sor ugyanezt a mintát követi:
A ciklus utasításait az első iterációra végrehajtva a váltorók a következő értékeket tartalmazzák:
Változó | Érték |
$line | 'Ana vs Dave | 3:0' |
$pairing | 'Ana vs Dave' |
$result | '3:0' |
$p1 | 'Ana' |
$p2 | 'Dave' |
$r1 | '3' |
$r2 | '0' |
Ezután a program összeszámolja, hogy melyik játékos hány szettet nyert:
+= $r1 azt jelenti, hogy a bal oldali változóban tárolt értéket megnöveljük az $r1-ben tárolt értékkel. Az első iterációnál %sets{$p1} még nem létezik, így alapértelmezett értékként egy Any-nek nevezett speciális értéket kap. Az összeadás és kivonás műveletek ezt a speciális értéket számként 0-nak kezelik.
Mielőtt ez a két sor végrehajtódna a $sets üres. Ha olyan kulcsú értékkel akarunk műveletet végezni, ami még nincs a hash-ben, akkor a kulcs azonnal létrejön, 0 kezdeti értékkel. (Ezt nevezik autovivification-nek.) Ennek a 2 sornak az első végrehajtása után $sets a következő értékeket tartalmazza: 'Ana' => 3, 'Dave' => 0. (=> jel a kulcs és érték párokat kapcsolja össze.)
Ha $r1 értéke nagyobb $r2-nél akkor %matches{$p1} értéke 1-el növekszik. Ellenkező esetben %matches{$p2} értéke növekszik 1-el. Ugyanúgy, mint += művelet esetében, ha a kulcs érték nem létezett előtte, akkor automatikusan létrejön. $thing++ ugyanazt jelenti, mint $thing = $thing + 1 vagy $thing += 1, azzal a kivétellel, hogy $thing += 1 még a növelés előtti értékkel tér vissza.
Ez a sor három egyszerű lépést hajt végre. Egy tömb sort függvénye visszatér a rendezett tömbbel. Alapértelmezett esetben egy tömb az értékei szerint van rendezve. Ha a játékosokat a nyerő sorrendben akarjuk megjeleníteni, akkor a játékosok pontja alapján kell rendeznünk a tömböt. Így a sort függvény paramétere egy blokk, ami átalakítja a játékosok neveit arra az adatra, amire szükségünk van a rendezéshez.
Ha a tömb két elemének az értéke azonos, akkor a rendezés ugyanabban a sorrendben hagyja őket, ahogy találta. Ezt stabil rendezésnek hívják.
A sort függvény növekvő sorrendbe rendezi az elemeket, a kisebbtől a nagyobb felé. Ezért szükség van a reverse függvény hívására, amely megfordítja a sorrendet. Végül eltároljuk az eredményt a @sorted változóban.
A játékosok és pontjaik kiírásához végig kell mennünk a @sorted tömb elemein, minden iterációnál a játékos nevét tárolva a $n változóban. Két kiíró függvény van: a say kiírja a paraméterként megkapott stringet, majd utána egy új sort, a print függvény ugyanezt csinálja, csak nem tesz új sort a végére.
Amikor futtatjuk a programot láthatjuk, hogy az eredmény kiírásakor $n helyére automatikusan behelyettesődik a változó az értéke. Ez azonban csak a " között szereplő stringeknél van így, ha ' jelek közé tesszük a stringet, akkor nem történik helyettesítés:
A " jelek közé írt stringben a Perl 6 helyettesíti a $ jellel jelölt változókat és a { és } jelek közé írt blokkokat az értékükre. Tömbök elemeinek kiírásánál az elemek közé space kerül, míg hashek kiírásánál az elemek új sorba kerülnek.
Ha tömbök és hash-ek { és } nélkül jelennek meg a " jelek között, akkor nem történik behelyettesítés, kivétel:
1. Az input megadására a példa feladatnál sok lehetőség van: az első sor tartalmazza az összes játékos nevét, ami felesleges, hiszen a játékosok neveit visszafejthetjük a lejátszott meccsek eredményeiből is. Kérdés: Hogyan néz ki a program az első input sor nélkül? Válasz: Töröljük a my @names = $file.get.split(' '); sort és változtassuk meg ezt a sort:
... erre:
2. Ahelyett, hogy törölnénk az első input sort, használhatjuk ellenőrzésre, például: ha olyan játékos jelenik meg az eredményeknél, amelyik nem volt benne az első sorában az inputnak. Ehhez létre kell hoznunk egy új hash változót, aminek a kulcsai a játékosok nevei és, ha olvasunk egy nevet leellenőrizzük, hogy benne van-e a hash táblában:
Az operátorok gyakran használt rutinok rövid nevei. Speciális szintaxisuk van és tudják kezelni egymást. Tekintsük az asztalitenisz-bajnokság példát az előző fejezetből. Tegyük fel, hogy ábrázolni akarjuk az egyes játékosok által nyert set-eknek a számát. A következő egyszerű példaprogram egy nyertes szettet egy db X-el ábrázol:
A program outputja:
Ez a sor:
... már eleve három különböző operátort tartalmaz: =, => és ,. Az = operátor az értékadás operátor, ami veszi a jobb oldal értékét és eltárolja a bal oldali változó értékeként. Mint sok más programozási nyelvben, a Perl 6-ban is megengedettek rövidített értékadások. Minden $var = $var op EXPR értékadást leírhatunk $var op= EXPR alakban is. A => operátor pár objektumokat képez. Egy pár tárol egy kulcsot és egy értéket: a kulcs az operátor bal oldalán az érték a jobb oldalán szerepel. Jellegzetessége, hogy az elemző minden kulcsot stringként értelmez, így az előző példát így is írhatnánk:
Végezetül a , operátor egy csomagot készít, ami objektumok sora. Ebben a pédában az objektumok párok. Mindhárom operátor úgynevezett infix operátor, ami azt jelenti, hogy két kifejezés között áll. Egy kifejezés lehet literál vagy bármilyen helyes kombinációja kifejezéseknek és operátoroknak.
Az előző fejezet más típusú operátorokat is használt. Például a %games{$p1}++; kifejezésben a {...} postcircumfix operátor, amely egy kifejezés után fordulhat elő, és egy kifejezést fog közre. Ezután az operátor után egy általános postfix operátor található: ++. Ez növeli a módosítandó kifejezés értékét eggyel. Egy kifejezés és annak postfix vagy postcircumfix operátorai között nem lehet space. Egy másik operátortípus a prefix operátor, ami egy kifejezés előtt fordulhat elő. Például: -, ami negálja a numerikus értékeket. A - operátor jelenthet még kivonást is. Hogy megkülönböztessük a prefix operátort az infixtől, a Perl 6 elemző figyel rá, hogy egy infix operátor vagy egy kifejezés követi-e. Egy kifejezésnek bárhány prefix operátora lehet.
A következő sor újabb jellegzetességeket tartalmaz:
Az infix max operátor két érték közül a nagyobbal tér vissza, így 2 max 3 eredménye 3. A szögletes zárójelek egy infix operátor körül az operátor egy listára való alkalmazását jelentik. Így [max] 1, 5, 3, 7 ugyanazt jelenti, mint 1 max 5 max 3 max 7. Ennek mintájára írhatunk [+]-t is, ami összegzi egy listában szereplő értékeket vagy [<=]-t a monoton növekedés ellenőrzésére. Ezután a @scores>>.key>>.chars kifejezés következik. @variable.method a @variable változóra hívja meg a method függvényt, ennek mintájára a @array>>.method kifejezés jelentése: @array tömb minden elemére meghívódik a method függvény, és a visszatérési értékek listájával tér vissza. Így tehát @scores>>.key>>.chars a @scores tömb pár elemei kulcsai hosszának a listája.
A program következő lépésben megállapítja a maximális pontszámot:
A rajzolható terület méretéből az X karakter mennyiségét:
Így már minden szükséges információ adott az eredmény kiíratásához:
Ez a ciklus a @scores változó elemein megy végig, az értékeket egy speciális változóhoz kötve. Minden elemre a program alkalmazza a beépített printf függvényt. Ez a függvény vesz egy format stringet, ami meghatározza, hogyan írja ki a megadott paramétereket. Ha a $label-area-width változó értéke 8, akkor a format string a következő: "%- 8s%snn", ami egy stringet jelent ('s'), space-ekkel 8 hosszúra kiegészítve (' 8') és balra zártan ('-'). Ezt követi egy újabb string és egy új sor. Az első string a játékos neve, a második pedig a pontszámot jelképező X-ek.
Az infix x operátor, vagy ismétlő operátor generálja ezt stringet. Ez veszi a stringet a bal oldaláról és egy számot a jobb oldaláról, és annyiszor írja egymás mellé a stringet, amennyit a szám meghatároz. .value a pár érték részével tér vissza.
A következő utasításban:
Az egyenlőség jobb oldala egy listát hoz létre (a , operátor miatt), ami párokból áll (=> operátor miatt), és az eredmény erre a tömbre mutat. A Perl 5 ezt még így értelmezte volna:
Így @scores csak egy elemet tartalmazna.
A precedencia szabályok meghatározzák, hogy milyen sorrendben értékelje ki az elemző a kifejezéseket. A Perl 6 nyelv operátorainak precedenciája szerint az infix => operátor tagjai szorosabban összetartoznak, mint az infix , operátor tagjai, aminek nagyobb a precedenciája, mint az értékadás operátornak.
A Perl 6 precedencia szabályai megengedik, hogy sok általános számítást természetes módon használjunk, anélkül, hogy gondolnunk kéne a precedenciára. Ha más tagolásra akarjuk kényszeríteni, akkor zárójeleket kell használnunk:
Sok módja van annak, hogy objektumokat összehasonlítsunk. Az értékek ekvivalenciáját az === infix operátorral ellenőrizhetjük. Állandó objektumoknál ez általános érték-összehasonlítás, de változékony objektumoknál azonosságot ellenőriz. Két objektum csak akkor azonos, ha a két objektum ugyanaz.
Példa | Név |
(tightest precedence) | |
(), 42.5 | term |
42.rand | method calls and postcircumfixes |
$x++ | autoincrement and autodecrement |
?$x, !$x | boolean prefix |
+$x, ~$x | prefix operators |
2*3, 7/5 | multiplicative infix operators |
1+2, 7-5 | additive infix operators |
$x x 3 | replication operators |
$x ~".nn" | string concatenation |
1&2 | junctive AND |
1|2 | junctive OR |
abs $x | named unary prefix |
$x cmp 3 | non-chaining binary operators |
$x == 3 | chaining binary operators |
$x && $y | tight AND infix |
$x || $y | tight OR infix |
$x > 0 ?? 1 !! -1 | conditional operator |
$x = 1 | item assignment |
not $x | loose unary prefix |
1, 2 | comma |
1, 2 Z @a | list infix |
@a = 1, 2 | list prefix, list assignment |
$x and say "Yes" | loose AND infix |
$x or die "No" | loose OR infix |
; | statement terminator |
(loosest precedence) |
Az eqv operátor csak akkor tér vissza True-val, ha az összehasonlított két objektum azonos típusú és azonos struktúrájú.
Az == infix operátorral megtudhatjuk, hogy két objektumnak azonos-e a numerikus értéke. Ha valamelyik objektum nem numerikus, akkor a Perl átalakítja az összehasonlítás előtt. Ha nem tudja megfelelően átalakítani, akkor az alapértelmezett 0 értéket használja.
A <, <=, >=, > operátorok a számok relatív méretét hasonlítják össze. Ha egy tömböt vagy listát használunk számként, akkor kiértékelésnél az elemek számát adja vissza.
Ahogy a számoknál az == operátor a tagokat számmá konvertálja, úgy az infix eq operátor stringek közötti egyenlőséget vizsgál, ha szükséges a tagokat stringgé konvertálva:
Más operátorok lexikografikusan hasonlítják össze a stringeket.
Számok összehasonlítása | Stringek összehasonlítása | |
== | eq | equals |
!= | ne | not equal |
!== | !eq | not equal |
< | lt | less than |
<= | le | less or equal |
> | gt | greater than |
>= | ge | greater or equal |
Például: 'a' lt 'b' igaz, és 'a' lt 'aa' is.
A három lehetőségű összehasonlítás operátorok két kifejezés összehasonlításakor három értéket adhatnak: ha a bal oldal kisebb a jobb oldalnál akkor Order::Increase, ha egyenlőek, akkor Order::Same, ha a jobb oldal a kisebb, akkor Order::Decrease értékkel tér vissza. Számoknál ez az operátor a <=>, stringeknél pedig a leg. Az infix cmp operátor összehasonlításnál, ha számokat hasonlít össze <=> operátorral, ha stringeket akkor a leg operátorral, párok esetén először a kulcs alapján, ha azok megegyeznek, akkor az értékek alapján hasonlít:
A három lehetőségű összehasonlítás tipikus használata a rendezés:
A különböző összehasonlító operátorok mind kényszerítik a paramétereiket bizonyos típusokra mielőtt elvégeznék az összehasonlítást. Ez hasznos, ha pontosan tudjuk, hogy milyen összehasonlításra van szükségünk, de a típusukban nem vagyunk biztosak. A Perl 6-ban lehetőség van egy másik operátor használatára, amit ~~-vel jelölünk és smart match operátornak hívunk:
A smart match operátor mindig az összehasonlítás jobb oldalán lévő érték típusa alapján dönti el az összehasonlítás típusát.
Az alprogram a kód egy része, amely speciális feladatot hajt végre. Megadott adatokat (paramétereket) dolgoz fel és eredményt állíthat elő (visszatérési érték). Egy alprogramot az összes paraméterével és visszatérési értékével adhatunk meg.
Egy alprogram deklaráció számos részból áll. Először is a sub kulcsszóból, ami jelzi, hogy egy alprogram deklaráció következik. Ezután egy tetszőleges név és egy tetszőleges paraméterezés következik. Ezt az alprogram törzse követi { és } jel között. Ez az a program részlet, ami az alprogram minden hívásakor lefut. Például:
A példában az alprogram neve panic és nincs paramétere. A törzse egy egyszerű say utasítás. Alapértelmezésben egy alprogram hatásköre a blokk, amelyben létrehozták, ugyanúgy mint a my kulcsszóval deklarált változóké. Ez azt jelenti, hogy csak abban a blokkban lehet meghívni az alprogramot, amelyben deklarálták. Az our kulcsszó használatával, lehetővé tesszük egy alprogram hívását azon a blokkon kívül is, ahol deklarálva lett:
Az our kulcsszó láthatóvá teszi az alprogramot a csomagon és a modulon kívül is:
Ahhoz, hogy egy alprogram egy másik blokkban is elérhető legyen, exportálni kell. Perl 6-ban az alprogramok objektumok. Átadhatjuk és különböző adatstruktúrákban tárolhatjuk őket, mint bármely más adatokat.
Egy alprogram szignatúrája két feladatot lát el. Először is deklarálja az argumentumokat, amiket a hívónak meg kell adnia az alprogramnak a hívásakor. Ezután deklarálja a változókat az alprogramban, melyek az argumentumokhoz kötődnek. Ezeket hívják paramétereknek. A Perl 6 szignatúrája korlátozhatja az argumentumok értékét és eltávolíthatja komplex adatszerkezetek részeit.
A szignatúra legegyszerűbb megadása egymástól vesszővel elválasztott változónevekkel lehetséges. Ezekhez lesznek hozzákötve a bejövő paraméterek.
A szignatúrában szereplő változók csak olvasható referenciái az átadott paramétereknek. A bejövő értékeket nem lehet megváltoztatni az alprogramban. Ha a read-only megkötés túlzottan korlátozó, akkor enyhíthetünk a megkötésen. Egy paraméter 'is rw'-vel megjelölve azt jelenti, hogy az átadott paramétert az alprogramon belül megváltoztathatjuk. Minden módosítás megváltoztatja az eredeti értéket. Ha egy szót vagy más konstans értéket akarunk átadni egy írható paraméternek, akkor az átadás az alprogram hívásakor hibát ad:
Ha az alprogramban a paraméter másolatával szeretnénk dolgozni, hogy az eredeti érték ne változzon, akkor ezt a paramétert meg kell jelölnünk a copy kulcsszóval:
Mivel egy alprogram több értékkel való visszatérésének szimulálásához sok nyelvben szükség van az 'as rw' megjelölésre, a Perl közvetlenül engedélyezi a több értékkel való visszatérést.
Egy változó jele utal a változó jövőbeli értékére. Egy szignatúrában a változó jele megköti az átadandó paraméter típusát. Például a @ jellel jelölt változónál leellenőrzi, hogy az átadott érték illeszkedik-e az aktuális típusra, esetünkben például tömbre vagy listára.
Hasonlóan, a % jel arra utal, hogy a hívónak valami asszociatív értéket kell átadnia. A & jel egy hívható adatot igényel, például egy ismeretlen alprogramot. Ebben az esetben a meghívandó alprogramot a & jel nélkül kell hívni:
A $ jel nem utal semmilyen megkötésre. Bármit hozzá lehet kötni még akkor is, ha más jelekhez is lehetne.
Néha szeretnénk a pozicionális paramétereket egy tömbből feltölteni ahelyett, hogy egyesével megadnánk: eat(@food[0], @food[1], @food[2], ...). Kisimíthatjuk a tömböt az paraméter listára a következő jelöléssel: eat(|@food). Hasonlóan lehet hash-eket interpolálni:
Néha nem szükséges átadni egy paramétert. Másoknak lehetnek alapértelmezett értékei. Az ilyen paramétereket megjelölhetjük opcionálisnak, így az alprogram hívásakor választhatunk, hogy használunk-e egy paramétert. Alapértelmezett értéket megadhatunk az alprogram szignatúrájában:
... vagy hozzáfűzünk egy kérdőjelet a paraméter nevéhez, ami azt jelenti, hogy a paraméter undefined értéket vesz fel, ha nem adódik át neki érték:
Ha egy alprogramnak sok paramétere van, könnyebb átadni a paramétereket a nevük alapján, mint emlékezni a paraméterek sorrendjére. Ebben az esetben a paraméterek megadásának sorrendje nem számít:
Kiköthetjük, hogy egy bejövő érték csak akkor adódik át egy paraméternek, ha név szerint történik az átadás. Ezt a paraméter neve előtti kettősponttal jelöljük:
A pozicionális és a név alapján megadott paraméterek sem kötelezőek. Ha kötelezővé akarjuk tenni őket, akkor a nevük utáni felkiáltójellel kell ezt jeleznünk:
Mivel név szerinti paraméterátadásnál a paraméterek nevét kell használni, ezért azok részei az alprogram publikus API-jának. Óvatosan kell megválasztani őket. Néha kényelmes felfedni egy paramétert másik névvel, mint amivel a változóhoz kötjük:
A paramétereknek lehet több nevük is. Például ha a felhasználók egy része brit, a másik része pedig amerikai:
Név szerinti paraméterek tulajdonképpen kulcs-érték párok. A párokat sokféleképpen leírhatjuk. A megközelítések közötti különbségek elsősorban a átláthatóság és a hivatkozási mechanizmusok. A következő három hívás ugyanazt jelenti:
Ha boolean értéket akarunk átadni, akkor elhagyhatjuk a pár érték részét:
Ha egy név szerint illesztendő paraméternek, például :name nincs értéke, akkor kap egy implicit Bool::True értéket. Ennek a negált alakja :!name, ami egy implicit Bool::False értéket kap. Ha egy pár létrehozásához használunk egy változót, akkor ezt újra használhatjuk a pár kulcsaként:
C<Pair> formák és jelentésük
Rövid forma | Hosszú forma | Leírás |
:allowed | allowed => Bool::True | Boolean flag |
:!allowed | allowed => Bool::False | Boolean flag |
:bev<tea coffee> | bev => ('tea', 'coffee') | List |
:times[1, 3] | times => [1, 3] | Array |
:opts{ a => 2 } | opts => { a => 2 } | Hash |
:$var | var => $var | Scalar variable |
:@var | var => @var | Array variable |
:%var | var => %var | Hash variable |
Ezen formák közül bármelyiket használhatjuk bárhol, ha párokkal dolgozunk. Például:
Ha pozicionális és név szerint illesztendő paramétereket is használunk egy szignatúrában, akkor minden pozicionális paraméternek a név szerinti paraméterek előtt kell lennie.
Amég a kötelező pozicionális paramétereknek az opcionálisak előtt kell lennie, addig a név szerinti paramétereknél nincs ilyen megkötés.
Egy előző példában a 'shout-it' függvénynek egy tömb paramétert kellett megadni. Ez megakadályozta a felhasználót, hogy egyszerű értéket adjon át. Engedélyezhetjük mindkét lehetőséget, kisimítva a pozicionális egyszerű és tömb paramétereket egy tömb paraméterré. Ezt a neve előtti *-gal jelöljük:
Egy ilyen paraméter tartalmazza az összes visszamaradt kötetlen, pozicionális paramétert egy tömbben. Hasonlóan *%hash tartalmazza az összes még nem megadott kötetlen és név szerinti paramétereket egy hash-ben.
Alprogramoknak lehet visszatérési értéke. Egy korábbi példában minden alprogram egy új stringgel tért vissza:
Perlben egy alprogram több értékkel is visszatérhet:
Ha elhagyjuk a return utasítást, akkor a Perl az alprogramban végrehajtott utolsó utasítás értékeivel tér vissza. Ezt mutatja be a következő példa:
Ezzel azonban óvatosan kell bánni: a vezérlés menete egy alprogramban elég komplex, ha explicit return utasítást használunk az átláthatóbbá teszi a kódot. Általános szabály, hogy csak az egyszerűbb alprogramok esetében érdemes kihasználni az implicit return előnyeit. A return utasítás hatására az alprogram azonnal véget ér:
Sok alprogram nem tud tetszőleges paramétereket feldolgozni, ezért megköthetjük a paraméterek típusát. Így az alprogram hívásakor helytelen típusú paramétert megadva hibát kapunk.
A legegyszerűbb módja az alprogram által elfogadott értékek megkötésének az, ha a típus nevét a paraméter elé írjuk. Például egy alprogram, amely numerikus műveleteket hajt végre a paramétereivel megkötheti, hogy az argumentumok típusa Numeric legyen:
Ez a következő kimenetet adja:
Ha több paraméterre is van megkötés, akkor minden átadott értéknek meg kell felelnie a megkötéseknek.
Néha a típus nevével való megkötés nem elegendő a paraméterre vonatkozó követelmények megadásához. Ezért egy where blokkban megadhatunk további megkötéseket:
Mivel a műveletek csak nem negatív számokra értelmesek, ezért olyan megkötést adtunk a paraméternek, ami csak akkor ad True eredményt, ha az érték nem negatív. Ha ez a megkötés False értékkel tér vissza, akkor az ellenőrzés hibát fog adni, alprogram hívásakor. Lehetséges intervallummal adott megkötés is:
Paramétereket megköthetünk egy hash létező kulcsaival is:
Egy bizonyos értelemben a szignatúra paraméterek gyűjteménye. A capture ugyanezt a funkciót valósítja meg. Egy capture-nek van pozicionális és név szerinti része is, ami listaként és hash-ként működik.
Egy capture létrehozásához az n(...) szintaxist használhatjuk. Hasonlóan a listákhoz és hash-ekhez interpolálhatjuk a capture-t egy paraméterbe a | jel használatával:
Ez a program létrehozza capture-ök egy listáját. Mindegyik tartalmaz két pozicionális és egy név szerint illesztendő paramétert. Ezután végigmegy a tömbön, és annak minden elemére meghívja az act alprogramot a paraméter halmazokkal. A Perl 6 tehát lehetőséget ad egy hívás paramétereinek és a már előre megadott paraméterekkel való alprogramhívás elkülönítésére. Így egy paraméter halmazzal meghívhatunk több alprogramot vagy egy alprogramot meghívhatunk több paraméter halmazra. Ehhez tudni kell, hogy a paraméterek közül melyek pozicionálisak és melyek név szerintiek. Ellentétben a szignatúrákkal a capture-ök referenciaként működnek. A capture-ben szereplő minden változó egy referencia a változóra.
Minden hívás felépít egy capture-t a hívó oldalon és kicsomagolja azt a szignatúra szerint a hívott oldalon. Az is lehetséges, hogy írunk egy szignatúrát, amit a capture magához köt egy változóba. Ez különösen fontos, ha olyan alprogramot írunk, ami más alprogramokat hív meg ugyanazokkal az argumentumokkal.
Néha csak tömbök vagy hash-ek egy részével kell dolgoznunk. Csinálhatjuk általános feldarabolással vagy használhatunk szignatúra kötést:
A szignatúra kötéses megközelítést az alprogram szignatúrájában alkalmazva óriási erővel bír:
A szögletes zárójelek a szignatúrában azt jelzik a fordítónak, hogy lista-szerű paramétert vár. Ahelyett, hogy egy tömb paraméterhez kötné, kicsomagolja az argumentumokat több paraméterbe: egy skalárba az első elemet és egy tömbbe a maradékot. Ez a szignatúra a tömb paraméter megszorításaként működik: a szignatúra kötés egészen addig hibát ad, amég a lista a capture-ben nem tartalmaz legalább 1 elemet. Hasonlóan egy hash-t kibonthatunk a %(...) használatával a szögletes zárójelek helyett:
Ha az order-burger alprogramot ismételten használjuk, de gyakran french fries-zel, akkor érdemes ehhez külön alprogramot írni:
Ha a személyes rendelésünk mindig vegetáriánus, akkor írhatunk egy order-the-usual alprogramot:
A körrizés lehetőséget ad ezeknek az egzakt eseteknek a rövidebb leírására, létrehoz egy új alprogramot egy létezőből, paraméterekkel, amiket már beállítottunk. Perl 6-ban a .assuming eljárással körrizhetünk:
Az új alprogram olyan, mint bármely másik alprogram, és minden paraméterátadási lehetőség használható rajta, amit eddig megismertünk.
Az alprogramok és ezek szignatúrái objektumok ugyanúgy, mint minden más. Így megtudhatunk jellemzőket róluk, beleértve a paraméterek részleteit is:
A & jel után az alprogram nevét írva megkapjuk a megadott alprogramot reprezentáló objektumot. &logarithm.signature az alprogram szignatúrájával tér vissza, a .params eljárást meghívva a szignatúrára megkapjuk a paraméter objektumok listáját. A lista minden egyes objektuma tartalmazza egy paraméter részleteit.
Metódusok a Parameter osztályban
method | description |
name | the name of the lexical variable to bind to, if any |
type | the nominal type |
constraints | Any further type constraints |
readonly | True if the parameter has is readonly trait |
rw | True if the parameter has is rw trait |
copy | True if the parameter has is copy trait |
named | True if the parameter is to be passed by name |
named | names List of names a named parameter can be passed as |
slurpy | True if the parameter is slurpy |
optional | True if the parameter is optional |
default | A closure returning the default value |
signature | A nested signature to bind the argument against |
A következő program a függőség kezelést mutatja Perl 6-ban. Láthatjuk az egyéni konstruktor eseteket, privát és publikus attribútumokat, metódusok és különböző szignatúrák aspektusait Kevés kódot produkál, és az eredmény érdekes, helyenként hasznos lesz.
A Perl 6 a legtöbb nyelvhez hasonlóan a class kulcsszót használja az új osztály bevezetésére. Minden, ami az ezt követő blokkon belül van, az osztálydefiníció része. Önkényesen elhelyezhetünk kódokat, különböző blokkokat, de az osztályok általában deklarációkat is tartalmaznak. A példakód deklarációkat tartalmaz az állapotokhoz (amit a has kulcsszóval vezetünk be), illetve a különböző viselkedésekhez(amit a method kulcsszóval vezetünk be).
Osztály deklarálásánál létrejön egy típus objektum, amely alapértelmezés szerint telepítésre kerül a csomagba (csakúgy, mint egy változó a hatókörben). Ez a típusú objektum egy "empty instance", "üres példánya" lesz az osztálynak. Például az Int és Str típusok, melyek olyan típus objektumok, amelyek a Perl 6-ban beépített osztályok. A példában szereplő osztály neve Task, a későbbi kódrészletekben lehet rá hivatkozni, de csak miután a new metódussal létrehoztuk egy példányát.
A típus objektumok definiálatlanok (undefined), ha a rajta meghívott .defined metódus False értékkel tér vissza. Ezt a metódust lehet használni annak kiderítésére, hogy az adott objektumok típus objektum vagy nem:
A példában az első 3 sor az osztály blokkjában attribútumokat deklarál. Ezek azok az adattárolók, amelyeket az osztály minden példánya megkap. Ahogy a változóhoz sem férhetünk hozzá a hatókörükön kívül, ugyanúgy az osztály attribútumait sem érhetjük el kívülről. Ez a beágyazás az egyik kulcselve az objektum-orientált szemléletnek.
A & jel jelentése, hogy az attribútum valami hivatkozhatót reprezentál. A ! másodrendű sigil (twigil). Ebben az esetben a ! twigil azt jelenti, hogy az adott attribútum privát adattagja az osztálynak.
Mivel ez az adattag elemek egy tömbjét reprezentálja, ezért szükség van a @ jelre. A típus deklaráció ennél az adattagnál azt jelenti, hogy a tömb csak Task típusú osztály példányait tartalmazhatja.
A harmadik adattag reprezentálja a task végrehajtásának az állapotát:
Ennek a skalár adattagnak a típusa Bool. A Perl 6 felment minket a done értékhez hozzáférő függvény megírásától. Ha a ! jelet helyettesítjük .-tal , deklarálja nekünk a $!done -t és a hozzáférő függvényt. Ez pontosan azt jelenti, mintha ezt írtuk volna:
Fontos, hogy ez nem egy publikus attribútum, mint ahogy más nyelvekben ez engedélyezett, valóban megkapjuk a privát tároló helyét és a lekérdező metódust, anélkül hogy meg kellene írnunk kézzel azt.
Fontos, hogy a . twigil használata során létrejön egy metódus, ami egy csak olvasható hozzáférést nyújt az adattaghoz.
Az "is rw" kifejezés generál egy olyan hozzáférő függvényt, amelynek segítségével meg lehet változtatni az adattag értékét.
Amíg az adattagok az objektum állapotát írják le, addig a metódusok az objektum viselkedését befolyásolják. Az első metódus a new egy speciális metódus. A második metódus, add-dependency, amely egy új Task-ot ad az osztály dependency listájához.
Sok szempontból hasonlít az alprogramok deklarációjához. Két fontos különbséget kivéve: deklaráláskor a metódus hozzáadódik az aktuális osztály metódus listájához. A Task osztály minden példánya meg tudja hívni ezt a metódust a . metódus hívó operátor segítségével. Másodszor, a metódus meghívható egy speciális változóból, a self-ből.
Nincsenek paraméterek, az objektum adattagjaival működik. Először leellenőrzi, hogy a task befejeződött-e, ha igen nincs további teendő.
A for konstrukció segítségével végigiterálunk a task összes @!dependencies -ben található elemén. Ez az iteráció minden egyes elemet egy speciális változóba helyez: $_. A for előtt lévő .perform() metódushívás lehetővé teszi nekünk, hogy a tömb minden elemére aktuálisan meghívodjon ez a függvény.
Perl 6 sokkal liberálisabb konstruktorok tekintetében, mint más nyelvek. A konstruktor az, ami visszatér az osztály egy példányával. Mindazonáltal a konstruktorok ugyanúgy rendes metódusok. Az alapértelmezett konstruktort az alap Object osztályból örököljük, amit szabadon felüldefiniálhatunk:
A legnagyobb különbség a Perl 6 és más nyelvek konstruktorai között az, hogy Perl 6-ban a konstruktorok ténylegesen létrehozzák az objektumot magát. a legkönnyebben ezt a bless metódus hívásával érhetjük el, amit az Objectből öröklünk. A bless metódus gondoskodik az adattagok értekének beállításáról a kezdeti értékeknek megfelelően.
Az osztály elkészítése után létre tudjuk hozni az osztály példányait. Egyszerű task létrehozásához, dependencies nélkül, írjuk ezt:
A Task osztály deklarálásakor ez a típus objektum bekerül a névtérbe. Ez a típus objektum egy "empty instance", "üres példánya" az osztálynak, speciálisan egy példány, állapot nélkül. Ezen a példányon is lehet metódusokat hívni, amíg nem próbálunk hozzáférni az állapotához. A new -val létrehozunk egy új objektumot, és ezután tudunk rajta módosítani, illetve hozzáférni a már meglévő objektumhoz.
Végül a perform metódus rekurzív meghívásával a különböző függőségeken a következő kimenetet kapjuk:
Objektum orientált programozás lehetővé teszi az öröklődés használatával a kód újrafelhasználását. A Perl 6 támogatja az egyszeres és többszörös öröklést. Ha egy osztály egy másik osztályból öröklődik, akkor az összes metódusát örökli: legyen az method kulcsszóval bevezetett, vagy hozzáférő függvényként generált.
A Programmer osztály egy példánya használni tudja az Employee osztályban definiált metódusokat és hozzáférő függvényeket:
$programmer.code to solve(’turing problem’); $programmer.pay();
Természetesen, az osztályok felüldefiniálhatják az ős osztály metódusait és adattagjait. A példában a Baker osztály örökli a Cook osztály cook metódusát, majd felüldefiniálja azt.
A Baker osztály cook metódusának hívásánál a dispatcher először a Baker cook metódusát nézi, mielőtt egyel fejjebb menne, a szülő osztály cook metódusáig.
Egy osztályt több osztályból is tudunk származtatni. Ha több osztályból származtatunk, a dispécser tudja hogyan néz ki mindkét osztály, így tudja, hogy melyik metódust kell meghívni. Perl 6 a C3 algoritmust használja az öröklési hiearchia linealizálására.
Az összes elérhetővé tett metódus a Programmer és a Cook osztályban elérhető a GeekCook osztályban is.
Emellett van egy másik, ennél sokkal jobb koncepció a többszörös öröklődés megvalósítására. Bővebben a Szerepek fejezetben.
Önelemzés egy folyamat információk gyűjtésére a programunkban szereplő objektumokról, nem forráskód elemzés, sokkal inkább egy lekérdezés az objektum egyes tulajdonságairól.
Adott egy objektum $o, és egy osztálydefiníció az előző részből, feltehetünk pár kérdést:
a kimenet:
Az első két teszt az osztálynév lekérdezése. Ha az objektum annak az osztálynak a példánya, vagy annak egy örökölt osztálya, igaz értékkel tér vissza. Tehát a kérdéses objektum vagy Employee típusú, vagy olyan, amiből örököltettük, de nem GeekCook.
A .WHAT típus objektummal tér vissza, ami megmondja az egzakt típusát a $o-nak: jelen esetben Programmer.
$o.perl egy string-el tér vissza, amit futtatni is lehet mint Perl kód, illetve reprodukálja az eredeti objektumot.
Végül a $o.^methods(:local) készít egy listát a metódusokról. A :local argumentum csak azokat a metódusokat adja vissza, amik az Employee osztályban vannak definiálva, az örökölt metódusokat kihagyja.
A szintaxisa .^ metódushívás a szimpla pont helyett most azt jelenti, hogy az aktuális metódust a meta osztályon hívjuk ki. A meta osztály menedzseli az Employee osztály tulajdonságait, vagy más osztályokat amelyekben érdekeltek vagyunk. Ez a meta osztály egy másik megoldás az önelemzéshez:
Önelemzés nagyon hasznos funkció a hibakeresésre, a nyelv tanulására, illetve új könyvtárak megismerésére. Ha például egy függvény vagy egy metódus egy olyan objektummal tér vissza,amit nem ismerünk, akkor nyugodtan lekérdezhetjük a .WHAT -tal.
Egy szerep egy önálló, elnevezett és újrafelhasználható egység. Egy szerepet hozzá lehet adni fordítási időben az osztályhoz, vagy futási időben az objektumhoz. Ezt az absztrakt definíciót egy példán keresztül lehet a legjobban demonstrálni. A program egy egyszerű IRCBot-ot mutat be, ami ért pár utasítást.
A legfontosabb ebben a példában jelenleg az, hogy a KarmaKeeper és a NothingBot osztályok örökléssel megosztanak egymással néhány viselkedést az IRCBotból és megkülönböztetik egymás viselkedéseit oly módon, hogy különböző szerepeket hajtanak végre.
Az előző fejezetekben az osztályokról és a nyelvtanokról volt szó. A szerep egy másik típusa a package-nek. Az osztályokhoz és a nyelvtanokhoz hasonlóan, a szerep is tartalmazhat metódusokat ( regex belevételével ) és adattagokat. A szerep nem tud megállni önmagában, nem lehet példákkal szemléltetni. A szerep használatához be kell őt foglalni egy objektumba, osztályba, vagy nyelvtanba. Más objektum-orientált rendszerekben az osztályok két feladatot látnak el. Entitásokat reprezentálnak a rendszerben, modellekkel látják el, amikből példányokat hozunk létre. Tehát a kódújrafelhasználást abszolválják. Ez a két feladat bizonyos fokig ellent mond egymásnak. Optimális újrafelhasználásnál az osztályok kicsik, de egy komplex entitás reprezentálásánál a kód egyre nagyobb lesz.
A Perl 6 osztályok felelőssége a példányok modellezése és managelése. A szerepek feladata a kódújrafelhasználás. A szerepek tartalmazzák a metódusokat és az attribútumokat. A szerepek nélkül készített osztály biztonságosnak mondható, ezt "flattening composition"-nak (összelapított összetétel) hívják. A szerepeket fel tudjuk használni egyéni objektumainkhoz. Mindkettő megjelenik a példakódban. Perl 6 ily módon nyújt lehetőséget a generikus programozásra, ugyanúgy mint a C# Java generic, vagy a C++ templates.
Nézzük meg a KarmaKeeper osztályt deklarációját. A törzs üres, az osztály nem definiál semmilyen adattagot vagy metódust a sajátjai közül. Az osztály az IRCBot-ból örököl, az "is" módosító segítségével, és a "does" módosító segítségével beilleszt két szerepet az osztályba.
A szerep összetételének folyamata egyszerű. A Perl veszi az adattagokat és metódusokat, amik a szerepben definiáltak, és átmásolja őket az osztályba. Ezután az osztály úgy jelenik majd meg, mintha a szerepen keresztül hozzáadott attribútumok és metódusok is az osztályban lettek volna definiálva. Ilyenkor csak azt kell megnézni, hogy az adott osztály elfogadja-e az adott szerepet. Ha a KarmaKeeper osztályon önelemzést végzünk, azt kapjuk hogy van process metódusa és on-message multi metódusa is.
AnswerToAll és AnswerIfTalkedTo szerepeknek van process methodusuk. Mindketten megosztanak egy nevet, a metódusok szemantikailag különböző működést hajtanak végre. A szerep összeállító készít egy fordítási-futási idejű hibát erről a konfliktusról és megkérdezi a programozót. Többszörös öröklés növeli ezen konfliktusn kialakulásának a kockázatát. Az öröklés sorrendje dönt, a szerepek egyforma prioritással bírnak. Ütközés esetén: a programozó hibát vét ha két szerepet is belefordít az osztályba és névütközés van. Választani kell hogy melyik viselkedés a mérvadó. Alternatív megoldás, ha beleírjuk az osztályba a metódust.
Osztályban és szerepben ugyanazon névvel szereplő metódusok esetén: a szerepben definiáltak figyelmen kívül maradnak. Osztályban definiált metódusok kiszorítják azokat a metódusokat, amik a szerepben vannak definiálva.
Néha minden rendben van a többszörös metódusokkal ugyanolyan névvel ellátva, különböző szignatúrájú metódusokkal, a multidispécser disztingválja őket. Az ugyanolyan nevű multimetódusok különböző szerepekből nem vezetnek konfliktushoz. Az összes jelölt az összes szerepből összesítve lesz a szerep összetétele során. Ha az osztály azonos nevű metódusokkal van ellátva, és multi, akkor az összes metódus ami a szerepben és az osztályban van definiálva, összesítve lesz egy multi jelöltek halmazba. Ha nem multi, akkor az osztályban definiált lesz érvényes. AdminBot osztály ki tudja választani a megfelelő on-message metódust ami megfelel a KarmaTracking és a Oping szerepeknek.
A process metódus az AnswerToAll and AnswerIfTalkedTo szerepekből, egy módosított szintaxist használ metódus hívására.
A .* metódus hívás szintaxisa megváltoztatja a diszpécser szemantikáját. A reguláris kifejezésekben a jelentése nulla vagy több, itt a jelentése nulla vagy több összeillő metódus. Ha nem on-message multi jelöltet talált, akkor nem csinál hibát. Ha több mint egy, akkor a Perl mindegyiket meg fogja hívni, vagy keres a hiearhiában, vagy mindkettő. Van még két variáció: a .+ mohón hívja meg az összes metódust, de megáll kivéve ha nem tud meghívni legalább egyet. ?. megpróbál meghívni egyet, de hibával tér vissza ha kivételt dob.
AnswerIfTalkedTo szerep egy stub-ot deklarál bot-nick metódushoz, de sosem látja el implementációval:
Ebben a szerep kontextusba azt jelenti, hogy amelyik osztályba összeállítottuk a szerepet, mindenképpen bele kell tenni a bot-nick metódust. Vagy az osztályba, vagy másik szerepen keresztül, vagy a szülő osztályba. A IRCBot az utóbbit csinálja, az IRCBot definiál egy $!bot-nick nevű adattagot a hozzá tartozó hozzáférő függvénnyel.
Ha nem adjuk meg explicit, hogy melyik metódustól függ a szerep, a szerep összeillesztő nem fogja tudni fordítási időben hogy melyik létezik. Hiányzó metódusok futási idejű hibákat generálhatnak, a fordítási idejű verifikáció egy fontos funkciója a szerepeknek.
Oaztályok deklarációja fordítási időben gyakran elegendő, de néha hasznosnak tűnhet már létező objektumokhoz új viselkedéseket hozzáadni. Perl 6 megengedi futási időben azt, hogy új szerepeket adjunk már meglevő objektumokhoz.
Szerepek első osztályú entitások a Perl 6-ban, mint az osztályok. A %pluggables hashmap az on-message-en belül eltárol egy szerepet a $plug-in -ben. A does operator hozzáadja ezt a szerepet a $self-hez, nem a $self osztályához, hanem annak egy példányához. Innentöl kezdve az osztály rendelkezik az összes metódussal. Az osztálynak csak ez a példánya változott, a többi ugyanúgy megkaphatja ezeket a metódusokat.
A futási idő abban különbözik a fordítási időtől, hogy az elfogadott szerepben felülírja az összes metódust amelyek az osztályban szerepelnek. Olyan mintha írnánk egy névtelen alosztályt az objektum éppen aktuális osztályába, amibe beletettük a szerepet. A .* megtalálja mindkét metódust, amik egy vagy több szerep alapján kerültek az osztályba.
Többszörös szerepeket a does operátorral kell "listázni". Ebben az esetben olyan, mint fordítási időben, ekkor az összes belekerül a képzeletbeli névtelen alosztályba. Futási időben történik, így nem annyira biztonságos mint fordítási időben.
Futási idejű szerep alkalmazás a does segítségével helyben módosítja az objektumot: $x does SomeRole módosítja $x ben tárolt objektumot. Sokszor ez a módosítás nem az amit akartunk. Ebben az esetben a but operátort kell használni, ami klónozza az objektumot, megkapja a szerepet a klón, és visszatér magával a klónnal . Az eredeti objektum ugyanaz marad.
A reguláris kifejezés egy fontos fogalom az informatikában: egyszerű minták leírják a szöveg formáját. Mintaillesztés egy olyan folyamat, ami elfogadja az aktuális szöveget, ha az illeszkedik, egyezik egy adott mintával.
Gyakori hiba, hogy egy szót véletlenül duplán írunk le. Nehéz észrevenni ezeket a hibákat a szöveg újraolvasásával, de Perl-el könnyen megtalálhatjuk ezt regex használatával:
Egy regex legegyszerűbb esete egy konstans string. Egy ilyen regex-re illesztve a stringet, megkeresi azt:
Az m/ ... / szerkezet egy regex-et épít fel. A regex-et a ~~ operátor jobb oldalán alkalmazzuk a bal oldalon lévő stringre. Space karakter a regex-en belül jelentéktelen, így m/ perl /, m/perl/ vagy m/ p e rl/ mindegyike ugyanazt az eredményt adja.
Csak betűket, számokat és az aláhúzást használhatjuk egzakt substring keresésnél. Minden más karakternek speciális jelentése van. Ha például egy vesszőre, csillagra vagy más nem betű karakterre akarunk keresni, akkor ' vagy \ jelet kell elé tennünk:
Regex-el nem csak betű szerint kereshetünk, hanem a speciális karaktereket is támogatja. Például a pont (.) bármilyen egyszerű karakterre illeszkedik:
Ez a következő outputot generálja:
A pont illeszkedett egy l, r és egy n karakterre, de illeszkedhet egy space-re is a mondatban. A $/ speciális változó tárolja a stringnek azt a részét, ami illeszkedik a reguláris kifejezésre.
Tegyük fel, hogy meg akarunk fejteni egy keresztrejtvényt. Van egy szólistánk, amiben meg akarjuk találni a szavakat, amik tartalmazzák a pe részstringet, utána egy tetszőleges betűt, majd egy 1-est (de nem space-t). A megfelelő regex ehhez: m/pe \w 1/.
Backslash sequences and their meaning
Szimbólum | Leírás | Példa |
\w | word character | l, ö, 3, |
\d | digit | 0, 1 |
\s | whitespace | (tab), (blank), (newline) |
\t | tabulator | (tab) |
\n | newline | (newline) |
\h | horizontal whitespace | (space), (tab) |
\v | vertical whitespace | (newline), (vertical tab) |
Ezeknek a jelentése megfordul, ha nagybetűvel írjuk őket. Például \N illeszkedik minden egyszerű karakterre, ami nem újsor.
Definiálhatunk saját karakter osztályt, az <[...]> jelek között felsorolt megfelelő karakterekkel:
Ahelyett, hogy a karakter osztály minden lehetséges karakterét egyesével felsorolnánk, használhatjuk karakterek sorozatát. Ezt a kezdő és a befejező karakter közé tett .. jellel jelöljük:
Hozzáadhatunk vagy elvehetünk karaktereket a karakter osztályokból a + és a - jelekkel:
Egy kvantál meghatározza, hogy valami hányszor fordulhat elő. A kérdőjel például opcionálissá teheti az egységet (bármi legyen is az). Így az m/ho u? se/ regex illeszkedik a hose és a house szavakra is. Space-k nélkül is csak az u betűre vonatkozik a ?. A * az egység 0 vagy több előfordulását jelenti. Így m/z\w*o/ illeszkedik a zo, zoo, zero szavakra is. A + jel egyszeri vagy többszöri előfordulást jelent: \w+ a szavakra illeszkedik. A legáltalánosabb kvantál a **. Ha egy szám követi, akkor az az ismétlődések számát jelenti. Ha egy intervallum van utána, akkor az intervallum bármely értéke lehet az ismétlődés mértéke:
Ha a jobb oldalán nem szám, és nem is intervallum áll, akkor határolójellé válik. Például m/ \w ** ', '/ karakterek listájára illeszkedik, melyek vesszővel és space-el vannak elválasztva egymástól. Ha egy kvantálnak többféle illeszkedése is lehetséges, akkor a Perl mindig a leghosszabbat választja. (A kvantál után kérdőjelet téve ezt megfordíthatjuk.)
A paragraph
And a second one
'; if $html ~~ m/ '' .* '
' / { say 'Matches the complete string!'; } if $html ~~ m/ '' .*? '
' / { say 'Matches onlyA paragraph
!'; }Ha egy módosítót több mint 1 karakterre vagy karakter osztályra akarjuk alkalmazni, akkor csoportosítani kell őket szögletes zárójelekkel:
Az alternatívákat | jellel különíthetjük el. Egy ilyen jel a regex több része között azt jelenti, hogy az alternatívák párhuzamosan illesztődnek, és a hosszabb illeszkedés nyer. Több ilyen jel a regex részei között azt jelenti, hogy minden alternatívára sorban próbálja az illeszkedést és az első illeszkedő alternatíva nyer.
Eddig minden regex a stringen belül bárhova illeszkedhetett. Gyakran hasznos, ha az illeszkedést egy string elejére, végére, sorra, vagy szóra kötjük. A ^ jel egy string elejét, míg a $ jel a string végét jelenti. m/ ^a / azokra a stringekre illeszkedik, amelyek a-val kezdődnek.
Ha egy regex-et zárójelek közé teszünk, akkor a Perl elraktározza a stringeket, amikre illeszkedik. Az első zárójeles csoportra illeszkedő stringet a $/[0] változóban lehet elérni, a másodika a $/[1] változóban tárolja és így tovább. $/ tartalmazza az összes illeszkedő stringet:
Regex anchors
^ | start of string |
$ | end of string |
^^ | start of a line |
$$ | end of a line |
<< | left word boundary |
>> | word boundary |
Ha egy capture-t kvantálunk, akkor a megfelelő bemenet az illesztendő objektumok listája:
Ez a következő kimenetet generálja: list: eggs | milk | sugar end: flour
Az első tárolt illeszkedés, (\w+) kvantált, így a $/[0] változó szavak listáját tartalmazza. A kódban a .join eljárás egy stringgé alakítja ezeket. Függetlenül attól, hogy az első illeszkedés hányszor teljesül, a második tárolt illeszkedés a $/[1] változóban tárolódik. A tárolt illeszkedések a $0 és a $1 nevekkel is elérhetőek. Így a duplikált szavakat felismerő regex:
A regex elejét egy szó elejéhez, végét pedig egy szó végéhez kötve nem illeszkedik a részleges duplikációkra.
Deklarálhatunk regex-eket ugyanúgy, mint alprogramokat és el is nevezhetjük őket.
A fenti kód bevezet egy word nevű regex-et, ami illeszkedik minden szóra, és egy db ' jellel elválasztott szavakra. A második dup nevű regex ehhez a regex-hez definiál duplikátum vizsgálatot. A regex-ben a <&word> megállapítja a word regex illeszkedését az aktuális szövegben. A <name=®ex> szinteaxissal létrehozhatunk egy tárolt illesztést name névvel, ami tartalmazza a ®ex illesztés eredményeként kapott objektumokat.
A névvel ellátott regex-ek lehetővé teszik bonyolultabb regex-ek kisebb elemekből való felépítését.
Az előző példa szavak listájára való illesztésre:
Ez működik, de nem elég elegáns megoldás. A kívánság, hogy a whitespace karaktereket bárhol megengedjük egy stringben elég általános. A Perl támogatja ezt a :sigspace (rövide :s) módosító használatával:
Ez a regex engedélyezi a whitespace karaktereket, de nem illeszkedik a eggs, milk, sugarandflour stringre. Az :ignorecase (röviden :i) módosító segítségével érzékennyé lehet tenni a regexet kis és nagybetűkre.
Egy regex-re való illesztés menetében eljöhet az a pont, amikor a regex egy része illeszkedik a stringre, de a string végén az ilesztés elbukik. Ebben az eseten, a regex-nek vissza kell lépnie és új alternatívával próbálkoznia. Ezt az elbukást és újra próbálkozást nevezzük backtrackingnek.
Például az oxen sting illesztésénél az m/\w+ 'en'/ regex-re, először az egész szóra illeszt a + jel miatt, de a végén az 'en' miatt elbukik az illesztés. Ekkor a \w+ elhagy egy karaktert és az oxe-re illeszkedik, de a végén az illesztés itt is elbukik. Ekkor újabb visszalépés után a \w+ az ox-ra illeszt és ebben az esetben az uolsó 2 betű illesztése is sikeres, így a teljes illesztés sikeres.
Amíg a backtracking gyakran hasznos és kényelmes, nagyon lassú és zavaró is tud lenni. A kettőspont segítségével kikapcsolhatjuk ezt az előző kvantáltra vagy alternatívára. Ekkor m/ \w+: 'en'/ soha egyetlen stringre sem fog illeszkedni.
A :ratchet módosító kikapcsolja a backtrackinget az egész regex-re, ami gyakran elvárt, ha egy sokszor hívott kis regex-et használunk más regex-ekből meghívva. A backtracking kikapcsolásával \w+ mindig egy teljes szóra illeszkedik:
A :ratchet módosító hatása csak abban a regex-ben érvényes, amiben megjelenik. A külső regex ettől függetlenül backtrackel.
A regex-ek adatok kezelésére is jók. A subst eljárás egy stringet illeszt egy regex-re, és helyettesíti a megtalált részeket a második operandusban megadott stringgel:
A subst eljárás egy sima illesztést hajt végre és leáll. A :g módosító hatására globálisan, minden lehetséges illesztésnél megtörténik a helyettesítés. m/.../ helyett az rx/.../ -et is használhatjuk regex létrehozására. Az előbbi létrehozza a regex objektumot. Az utóbbi létrehozás után rögtön el is végzi az illesztést az aktuális változóra ($).
A nyelvtanok a reguláris kifejezéseket szervezik össze úgy, ahogy az osztályok a metódusokat. A következő példa azt demonstrálja, hogyan parszol a JSON.
Egy nyelvtan különböző regex-eket tartalmaz. Regex nevei konstruktívan épülnek fel, úgyanugy mint az alprogramok vagy a metódusok nevei. A TOP szabály alapértelmezében akkor hívódik meg, amikor a .parse() metódust lefuttatjuk a nyelvtanon. JSON::Tiny::Grammar.parse($tester) hívás $tester stringet csak akkor fogadja el, ha az illeszkedik a TOP nevű szabályban megfogalmazott reguláris kifejezésre.
A TOP szabály anchor-okat tartalmaz a string elején és a végén, tehát az egész string érvényes JSON formátumú, ha elfogadásra kerül. Ezután megkísérli a regex-et összeilleszteni vagy az <array> -val vagy az <object> -val.
Regex-ek lehetnek rekurzívak is. Egy tömb értékeket tartalmaz. Fordítva egy érték lehet egy tömb. Ez addig nem lesz végtelen ciklus, amíg legalább egy karaktert "el nem pusztított" egy reguláris kifejezés rekurzív hívásonként. Ha egy regexek halmazának minden elemét meghívjuk rekurzívan, és nem haladunk előre a stringben, könnyen végtelen ciklusba kerülhetünk, és soha nem lesz elfogadva a nyelvtan.
Másik újdonság egy proto token deklarációja:
A proto token szintaxisa azt mutatja, hogy az érték az alternatívák egy halmaza lesz egy egyszerű reguláris kifejezés helyett. Minden alternatívának van egy neve, aminek a formája: sym<thing> amit így olvashatunk: alternative of value with parameter sym set to thing. A törzs egy normál regex ami meghívja a <sym>-et és illeszti a parmétert.
Ha meghívjuk a <value> szabályt, a nyelvtan motor párhuzamosan nézi az alternatívákat, és a leghosszabb győz.
A hasonlóság a nyelvtanok és az osztályok között több, mint a regex-ek tárolása egy névtérben. Lehetőség van nyelvtanok örököltetésére, kiterjesztésére, polimorfizmusra és szerepeket adhatunk hozzá. A nyelvtan egy osztály, ami a Grammar-ből örököl, és nem az Any-ből.
Szeretnénk növelni a JSON nyelvtanunkat, amely megengedi az egysoros kommenteket: // kezdődik és a sor végéig tart. JSON::Tiny::Grammar csak implicit fogadja el a whitespace-eket szabályok felhasználásával, amik olyanok mint a token-ek, csak a :sigspace módosítóval. Implicit whitespace elfogadásához ezzel az örökölt regexel lehetséges: <ws>, tehát csak ez a regexet kell felüldefiniálni:
Az első két sor bevezeti a nyelvtant, amit a JSON::Tiny::Grammar -ből örököltetünk. Ahogyan az alosztályok örökölnek az őssztályokból, néhány nyelvtani szabály, ami nincs prezentálva a származtatott nyelvtanban, az ősosztályból öröklődik. Ebben a minimal JSON nyelvtanban, a whitespace nem mindig megbízható, a ws nem mindent talál meg, illeszt. Az opcionális space-ek után, két slash '//' bevezeti a commentet, utána bármennyi számú nem newline karakter jöhet, utána egy newline. Tehát a '//' után a sor maradéka következik.
Ebben a nyelvtanban, a <value> hívása illeszti az újonnan hozzáadott alternatívákhoz, vagy a régi, az ősosztályban lévő alternatívákhoz.
A parse metódusa a nyelvtannak visszatér egy Match objektummal, amin keresztül hozzáférhetünk az összes lényeges információhoz. A Match objektum hasonló a hash-hez: a kulcsok a regex nevek, az értékek az adott regex illesztések. Hasonlóan elérhetők a szülő találatok is. Megvan a Match objektumunk, mit lehet kezdeni vele? Rekurzívan bejárjuk, és készítünk megfelelő adatstruktúrát, attól függően hogy mit akarunk vele kezdeni:
Ez a példa végig megy egy action objektumon, hogy parszolja a nyelvtant. Ha a regex befejezte a parszolást, az action objektumon meghív egy metódust ugyanazzal a névvel, mint az aktuális regex. Ha nincs ilyen, a nyelvtani motor megy tovább, ha van, akkor végigmegy az éppen aktuális találaton.
Minden Match objektumnak van egy slotja, ast -nek hívják(abstract syntax tree). Ez a slot tárolhat egyéni adatokat, amiket az action metódusból készítettünk. Make $thing hívás egy action metódusban beállítja az éppen aktuális objektum azt az attribútumot a $thing-be.
Abstract syntax tree vagy AST egy adatstruktúra, ami a parszolt szöveget tartalmazza. A nyelvtan egy AST struktúrát ír le: a gyökér elem a Top csomópont, ami tartalmazza a megengedett típusok gyerekeit, és igy tovább.
A szabályok és az action metódusok külön névtérben vannak, mint a való világban általában.
A TOP szabály szerint két alternatíva lehetséges: objektum vagy tömb. Mindkettőnek lehet "capture"-je. A $/.values egy listával tér vissza ami tartalmazza a "capture"- ket.
A redukáló metódus kiterjeszti a pairlist AST-jét altalálatokba, és a hash metódus meghívásával hashbe teszi őket.
A pairlist szabály vesszővel elválasztott párokat illeszt. A reduction metódus hívásakor meghívodik a .ast az összes találatra, és a saját AST-jébe rakja az eredményt.
A párok megadása a => operátorral: kulcs => érték.
Action metódus működése: a match objektumból kinyeri az információt, áttranszformálja natív Perl 6 adatstruktúrává, és a make függvény hívásával beállítja a nativ struktúrák AST -jét.
Ha a <value> hívás talált, az action metódus ugyanaz a szimbólum mintha egy alszabályt futtattunk volna.
Rengeteg operátor dolgozik különleges adattípusokkal. Ha az operandusok típusa eltér az operátor típusától, a Perl csinál egy másolatot az operandusról, és a szükséges típusúvá konvertálja. Például: $a + $b; $a és $b másolata átkonvertálódik számmá, hacsak nem eredetileg is számok voltak. Ezt az implicit konverziót kényszerítésnek hívják.
Operátorok mellett más szintaktikus elemeket is kényszeríthetünk: if és a while konvertálódik boolean értékké, és így tovább.
Néha a kényszerítés átlátszó. Perl 6 -nak létezik néhány numerikus típusa, amik szabadon keverhetők. Például egy lebegőpontos értéket kivonhatunk egy integerből: 123 - 12.1e1.
A legfontosabb típusok:
Int objektumok tárolják az önkényes mérettel rendelkező integer számokat. Ha leírunk egy literált, ami csupán számjegyekből áll, annak a atípusa Int.
Num a lebegőpontos szám típus. Tárolja az előjelet, a mantisszát, az exponenst, fix hosszúsággal. A számítás gyakran elég gyors, de korlátozott pontossággal.
Számok, tudományos jelöléssel, mint például a 6.022e23 Num típusúak.
Rat a rational rövidítése, tört számok veszteség nélküli pontossággal. Két egész szám hányadosaként ábrázoljuk,így a matematikai műveletek Rat típusokon meglehetősen lassúak lesznek. Ez okból nagy nevezőjű racionális számok automatikusan Num típusúvá konvertálódnak.
Tört érték írása a tizedespont segítségével, mint a 3.14, Rat típust eredményez.
Komplex számoknak két összetevője van: a valós és a képzetes. Ha mindkét rész NaN, akkor a teljes szám is NaN.
a + bi alakú számok, ahol a képzetes rész a bi, Complex típusúak.
A következő operátorok érhetőek el az összes szám típushoz:
A legtöbb matematikai függvény elérhető mind metódus, mind függvény formájában, így mindkettőt leírhatjuk: (-5).abs és abs(-5).
A trigonometrikus függvények: sin, cos, tan, asin, acos, atan, sec, cosec, cotan, asec, acosec, acotan, sinh, cosh, tanh, asinh, acosh, atanh, sech, cosech, cotanh, asech, acosech and acotanh; alapértelmezetten radiánt használva. Speciálisan lehet fok, gradiens és kör is.
Bináris numerikus operátorok:
Operátor | Leírás |
** | Exponentiation: $a**$b is $a to the power of $b |
* | multiplication |
/ | division |
div | integer division |
+ | addition |
- | subtraction |
Unáris numerikus operátorok:
Operátor | Leírás |
+ | conversion to number |
- | negation |
Matematikai függvények és metódusok:
Metódus | Leírás |
abs | absolute value |
sqrt | square root |
log | natural logarithm |
log10 | logarithm to base 10 |
ceil | rounding up to an integer |
floor | rounding down to an integer |
round | rounding to next integer |
sign | -1 for negative, 0 for zero, 1 for positive values |
A stringeket, karakterek sorozatát, az Str tárolja, karakterkódolástól függetlenül. A Buf típus elérhető bináris adat tárolására. Az encode metódussal lehet Str-t Buf-fá konvertálni, decode az ellenkező irányba.
A kövdetkező operátorok érhetőek el a stringekre:
Bináris sztring operátorok:
Operator | Description |
~ | concatenation: 'a' ~'b' is 'ab' |
x | replication: 'a' x 2 is 'aa' |
Unáris sztring operátorok:
Operátor | Leírás |
~ | conversion to string: ~1 becomes '1' |
Sztring függvények/metódusok:
függvények/metódusok | Leírás |
.chomp | remove trailing newline |
.substr($start, $length) | extract a part of the string. $length defaults to the rest of the string |
.chars | number of characters in the string |
.uc | upper case |
.lc | lower case |
.ucfirst | convert first character to upper case |
.lcfirst | convert first character to lower case |
.capitalize | convert the ?rst character of each word to upper case, and all other ch |
Boolean típusú váltoró értéke True vagy False lehet. Valamennyi érték kényszeríthető boolean értékké. Annak eldöntése, hogy True vagy False lesz az érték, függ az érték típusától:
Üres string vagy a "0" kiértékelése False. Minden más string kiértékelése True.
Az összes szám kiértékelése True, kivéve a nullát.
Container típusok, mint például a lista vagy a hash kiértékelése False, feltéve ha üresek, és True ha legalább egy elemet tartalmaznak.
Konstrukciók automatikusan kiértékelődnek a saját feltételeikkel. ? segítségével explicit kiértékelhetők. ! prefix negálja a boolean értéket.