A Perl programozási nyelv

Perl 6

Bevezetés

Alapok

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:

Beth Ana Charlie Dave Ana vs Dave | 3:0 Charlie vs Beth | 3:1 Ana vs Beth | 2:3 Dave vs Charlie | 3:0 Ana vs Charlie | 3:1 Beth vs Dave | 0:3

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:

use v6; my $file = open 'scores'; my @names = $file.get.split(' '); my %matches; my %sets; for $file.lines -> $line { my ($pairing, $result) = $line.split(' | '); my ($p1, $p2) = $pairing.split(' vs '); my ($r1, $r2) = $result.split(':'); %sets{$p1} += $r1; %sets{$p2} += $r2; if $r1 > $r2 { %matches{$p1}++; } else { %matches{$p2}++; } } my @sorted = @names.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse; for @sorted -> $n { say "$n has won %matches{$n} matches and %sets{$n} sets"; }

Ez a következő outputot adja:

Ana has won 2 matches and 8 sets Dave has won 2 matches and 6 sets Charlie has won 1 matches and 4 sets Beth has won 1 matches and 4 sets

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.

my $file = open 'scores';

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.

my @names = $file.get.split(' ');

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.

my %matches; my %sets;

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.

for $file.lines -> $line { ... }

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.

my ($pairing, $result) = $line.split(' | ');

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:

my ($p1, $p2) = $pairing.split(' vs '); my ($r1, $r2) = $result.split(':');

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:

%sets{$p1} += $r1; %sets{$p2} += $r2;

+= $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.)

if $r1 > $r2 { %matches{$p1}++; } else { %matches{$p2}++; }

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.

my @sorted = @names.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse;

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.

for @sorted -> $n { say "$n has won %matches{$n} matches and %sets{$n} sets"; }

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:

my $names = 'things'; say 'Do not call me $names'; # Do not call me $names say "Do not call me $names"; # Do not call me things

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.

say "Math: { 1 + 2 }" # Math: 3 my @people = <Luke Matthew Mark>; say "The synoptics are: {@people}" # The synoptics are: Luke Matthew Mark say "{%sets}"; # From the table tennis tournament # Charlie 4 # Dave 6 # Ana 8 # Beth 4

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:

my @flavours = <vanilla peach>; say "we have @flavours"; # we have @flavours say "we have @flavours[0]"; # we have vanilla # so-called "Zen slice" say "we have @flavours[]"; # we have vanilla peach # method calls ending in postcircumfix say "we have @flavours.sort()"; # we have peach vanilla # chained method calls: say "we have @flavours.sort.join(', ')"; # we have peach, vanilla

Példák

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:

my @sorted = @names.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse;

... erre:

my @sorted = %sets.keys.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse;

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:

... my @names = $file.get.split(' '); my %legitimate-players; for @names -> $n { %legitimate-players{$n} = 1; } ... for $file.lines -> $line { my ($pairing, $result) = $line.split(' | '); my ($p1, $p2) = $pairing.split(' vs '); for $p1, $p2 -> $p { if !%legitimate-players{$p} { say "Warning: '$p' is not on our list!"; } } ... }

Operátorok

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:

use v6; my @scores = 'Ana' => 8, 'Dave' => 6, 'Charlie' => 4, 'Beth' => 4; my $screen-width = 30; my $label-area-width = 1 + [max] @scoresź.keyź.chars; my $max-score = [max] @scoresź.value; my $unit = ($screen-width - $label-area-width) / $max-score; for @scores { my $format = '%- ' ~ $label-area-width ~ "s%snn"; printf $format, .key, 'X' x ($unit * .value); }

A program outputja:

Ana XXXXXXXXXXXXXXXXXXXXXX Dave XXXXXXXXXXXXXXXX Charlie XXXXXXXXXXX Beth XXXXXXXXXXX

Ez a sor:

my @scores = 'Ana' => 8, 'Dave' => 6, 'Charlie' => 4, 'Beth' => 4;

... 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:

my @scores = Ana => 8, Dave => 6, Charlie => 4, Beth => 4;

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:

my $label-area-width = 1 + [max] @scores>>.key>>.chars;

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:

my $max-score = [max] @scores>>.value;

A rajzolható terület méretéből az X karakter mennyiségét:

my $unit = ($screen-width - $label-area-width) / $max-score;

Így már minden szükséges információ adott az eredmény kiíratásához:

for @scores { my $format = '%- ' ~ $label-area-width ~ "s%snn"; printf $format, .key, 'X' x ($unit * .value); }

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.

Precedencia

A következő utasításban:

my @scores = 'Ana' => 8, 'Dave' => 6, 'Charlie' => 4, 'Beth' => 4;

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:

(my @scores = 'Ana') => 8, 'Dave' => 6, 'Charlie' => 4, 'Beth' => 4;

Í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:

say 5 - 7 / 2; # 5 - 3.5 = 1.5 say (5 - 7) / 2; # (-2) / 2 = -1

Összehasonlítás

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éldaNé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)

my @a = 1, 2, 3; my @b = 1, 2, 3; say @a === @a; # 1 say @a === @b; # 0 # these use identity for value say 3 === 3; # 1 say 'a' === 'a'; # 1 my $a = 'a'; say $a === 'a'; # 1

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ú.

Numerikus összehasonlítás

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.

say 1 == 1.0; # 1 say 1 == '1'; # 1 say 1 == '2'; # 0 say 3 == '3b' # 1

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.

my @colors = <red blue green>; if @colors == 3 { say "It's true, @colors contains 3 items"; }
String összehasonlítás

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:

if $greeting eq 'hello' { say 'welcome'; }

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.

Három lehetőségű (three-way) összehasonlítás

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:

say 10 <=> 5; # +1 say 10 leg 5; # because '1' lt '5' say 'ab' leg 'a'; # +1, lexicographic comparison

A három lehetőségű összehasonlítás tipikus használata a rendezés:

say ~<abstract Concrete>.sort; # output: Concrete abstract say ~<abstract Concrete>.sort: -> $a, $b { uc($a) leg uc($b) }; # output: abstract Concrete
Smart match

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:

if $pints-drunk ~~ 8 { say "Go home, you've had enough!"; } if $country ~~ 'Sweden' { say "Meatballs with lingonberries and potato moose, please." } unless $group-size ~~ 2..4 { say "You must have between 2 and 4 people to book this tour."; }

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.

Alprogramok

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.

Alprogram deklarálása

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:

sub panic() { say "Oh no! Something has gone most terribly wrong!"; }

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:

{ our sub eat() { say "om nom nom"; } sub drink() { say "glug glug"; } } eat(); # om nom nom drink(); # fails, can't drink outside of the block

Az our kulcsszó láthatóvá teszi az alprogramot a csomagon és a modulon kívül is:

module EatAndDrink { our sub eat() { say "om nom nom"; } sub drink() { say "glug glug"; } } EatAndDrink::eat(); # om nom nom EatAndDrink::drink(); # fails, not declared with "our"

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.

my $dance = ''; my %moves = hands-over-head => sub { $dance ~= '/on ' }, bird-arms => sub { $dance ~= '|/on| ' }, left => sub { $dance ~= '>o ' }, right => sub { $dance ~= 'o< ' }, arms-up => sub { $dance ~= 'no/ ' }; my @awesome-dance = <arms-up bird-arms right hands-over-head>; for @awesome-dance -> $move { %moves{$move}.(); } say $dance;

Szignatúra megadása

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.

Az alapok

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.

sub order-beer($type, $pints) { say ($pints == 1 ?? 'A pint' !! "$pints pints") ~ " of $type, please." } order-beer('Hobgoblin', 1); # A pint of Hobgoblin, please. order-beer('Zlatý Baľant', 3); # 3 pints of Zlatý Baľant, please.

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:

sub make-it-more-so($it is rw) { $it ~= substr($it, $it.chars - 1) x 5; } my $happy = "yay!"; make-it-more-so($happy); say $happy; # yay!!!!!! make-it-more-so("uh-oh"); # Fails; can't modify a constant

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:

sub say-it-one-higher($it is copy) { $it++; say $it; } my $unanswer = 41; say-it-one-higher($unanswer); # 42 say-it-one-higher(41); # 42

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.

Tömbök, hash-ek és kód átadása

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.

sub shout-them(@words) { for @words -> $w { print uc("$w "); } } my @last_words = <do not want>; shout-them(@last_words); # DO NOT WANT shout-them('help'); # Fails; a string is not Positional

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:

sub do-it-lots(&it, $how-many-times) { for 1..$how-many-times { it(); } } do-it-lots(sub { say "Eating a stroopwafel" }, 10);

A $ jel nem utal semmilyen megkötésre. Bármit hozzá lehet kötni még akkor is, ha más jelekhez is lehetne.

Tömbök és hash-ek interpolálása

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:

sub order-shrimps($count, $from) { say "I'd like $count pieces of shrimp from the $from, please"; } my %user-preferences = ( from => 'Northern Sea' ); order-shrimps(3, |%user-preferences)
Opcionális paraméterek

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:

sub order-steak($how = 'medium') { say "I'd like a steak, $how"; } order-steak(); order-steak('well done');

... 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:

sub order-burger($type, $side?) { say "I'd like a $type burger" ~ ( defined($side) ?? " with a side of $side" !! "" ); } order-burger("triple bacon", "deep fried onion rings");
Paraméterillesztés név alapján

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:

sub order-beer($type, $pints) { say ($pints == 1 ?? 'A pint' !! "$pints pints") ~ " of $type, please." } order-beer(type => 'Hobgoblin', pints => 1); # A pint of Hobgoblin, please. order-beer(pints => 3, type => 'Zlatý Baľant'); # 3 pints of Zlatý Baľant, please.

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:

sub order-shrimps($count, :$from = 'North Sea') { say "I'd like $count pieces of shrimp from the $from, please"; } order-shrimps(6); # takes 'North Sea' order-shrimps(4, from => 'Atlantic Ocean'); order-shrimps(22, 'Mediterranean Sea'); # not allowed, :$from is named only

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:

sub design-ice-cream-mixture($base = 'Vanilla', :$name!) { say "Creating a new recipe named $name!" } design-ice-cream-mixture(name => 'Plain'); design-ice-cream-mixture(base => 'Strawberry chip'); # missing $name
Paraméterek átnevezése

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:

sub announce-time(:dinner($supper) = '8pm') { say "We eat dinner at $supper"; } announce-time(dinner => '9pm'); # We eat dinner at 9pm

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:

sub paint-rectangle( :$x = 0, :$y = 0, :$width = 100, :$height = 50, :color(:colour($c))) { # print a piece of SVG that reprents a rectangle say qq[<rect x="$x" y="$y" width="$width" height="$height" style="fill: $c" />] } # both calls work the same paint-rectangle :color<Blue>; paint-rectangle :colour<Blue>; # of course you can still fill the other options paint-rectangle :width(30), :height(10), :colour<Blue>;
Alternatívák név szerinti paraméterillesztés szintaxisára

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:

announce-time(dinner => '9pm'); announce-time(:dinner('9pm')); announce-time(:dinner<9pm>);

Ha boolean értéket akarunk átadni, akkor elhagyhatjuk a pár érték részét:

toggle-blender( :enabled); # enables the blender toggle-blender(:!enabled); # disables the blender

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:

my $dinner = '9pm'; announce-dinner :$dinner; # same as dinner => $dinner;

C<Pair> formák és jelentésük

Rövid formaHosszú formaLeí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:

# TODO: better example my $black = 12; my %color-popularities = :$black, :blue(8), red => 18, :white<0>; # same as # my %color-popularities = black => 12, blue => 8, red => 18, white => 0;
Paraméterek sorrendje

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.

sub mix(@ingredients, :$name) { ... } # OK sub notmix(:$name, @ingredients) { ... } # Error

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.

"Slurpy" paraméterek

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:

sub shout-them(*@words) { for @words -> $w { print uc("$w "); } } # now you can pass items shout-them('go'); # GO shout-them('go', 'home'); # GO HOME my @words = ('go', 'home'); shout-them(@words); # still works

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.

sub debug-wrapper(&code, *@positional, *%named) { warn "Calling '&code.name()' with arguments " ~ "@positional.perl(), %named.perl()\n"; code(|@positional, |%named); warn "... back from '&code.name()'\n"; } debug-wrapper(&order-shrimps, 4, from => 'Atlantic Ocean');

Visszatérési érték

Alprogramoknak lehet visszatérési értéke. Egy korábbi példában minden alprogram egy új stringgel tért vissza:

my %moves = hands-over-head => sub { return '/on ' }, bird-arms => sub { return '|/on| ' }, left => sub { return '>o ' }, right => sub { return 'o< ' }, arms-up => sub { return 'no/ ' }; my @awesome-dance = <arms-up bird-arms right hands-over-head>; for @awesome-dance -> $move { print %moves{$move}.(); } print "\n";

Perlben egy alprogram több értékkel is visszatérhet:

sub menu { if rand < 0.5 { return ('fish', 'white wine') } else { return ('steak', 'red wine'); } } my ($food, $beverage) = menu();

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:

sub menu { if rand < 0.5 { 'fish', 'white wine' } else { 'steak', 'red wine'; } } my ($food, $beverage) = menu();

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:

sub create-world(*%characteristics) { my $world = World.new(%characteristics); return $world if %characteristics<temporary>; save-world($world); }

Típusok használata

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.

Alaptípusok

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:

sub mean(Numeric $a, Numeric $b) { return ($a + $b) / 2; } say mean 2.5, 1.5; say mean 'some', 'strings';

Ez a következő kimenetet adja:

2 Nominal type check failed for parameter '$a'; expected Numeric but got Str

Ha több paraméterre is van megkötés, akkor minden átadott értéknek meg kell felelnie a megkötéseknek.

Megkötések megadása

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:

sub circle-radius-from-area(Numeric $area where { $area >= 0 }) { ($area / pi).sqrt } say circle-radius-from-area(3); # OK say circle-radius-from-area(-3); # Error

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:

sub set-volume(Numeric $volume where 0..11) { say "Turning it up to $volume"; }

Paramétereket megköthetünk egy hash létező kulcsaival is:

my %in-stock = 'Staropramen' => 8, 'Mori' => 5, 'La Trappe' => 9; sub order-beer(Str $name where %in-stock) { say "Here's your $name"; %in-stock{$name}--; if %in-stock{$name} == 0 { say "OH NO! That was the last $name, folks! :'("; %in-stock.delete($name); } }

Capture

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.

Capture létrehozása és használata

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:

sub act($left, $right, :$action) { $action($left, $right); } my @tasks = n(39, 3, action => { say $^a + $^b }), n(6, 7, action => { say $^a * $^b }); for @tasks -> $task-args { act(|$task-args); }

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.

my $value = 7; my $to-change = n($value); sub double($x is rw) { $x *= 2; } sub triple($x is rw) { $x *= 3; } triple(|$to-change); double(|$to-change); say $value; # 42
Capture és szignatú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.

sub visit-czechoslovakia(|$plan) { warn "Sorry, this country has been deprecated."; visit-slovakia(|$plan); visit-czech-republic(|$plan); }
Capture kicsomagolása

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:

sub first-is-largest(@a) { my $first = @a.shift; # TODO: either explain junctions, or find a concise way to write without them return $first >= all(@a); } # same thing: sub first-is-largest(@a) { my :($first, *@rest) := n(|@a) return $first >= all(@rest); }

A szignatúra kötéses megközelítést az alprogram szignatúrájában alkalmazva óriási erővel bír:

sub first-is-largest([$first, *@rest]) { return $first >= all(@rest); }

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:

sub create-world(%(:$temporary, *%characteristics)) { my $world = World.new(%characteristics); return $world if $temporary; save-world($world); }

Körrizés

sub order-burger( $type, $side? ) { ... };

Ha az order-burger alprogramot ismételten használjuk, de gyakran french fries-zel, akkor érdemes ehhez külön alprogramot írni:

sub order-burger-and-fries ( $type ) { order-burger( $type, side => 'french fries' ); }

Ha a személyes rendelésünk mindig vegetáriánus, akkor írhatunk egy order-the-usual alprogramot:

sub order-the-usual ( $side? ) { if ( $side.defined ) { order-burger( 'veggie', $side ); } { order-burger( 'veggie' ); } }

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:

&order-the-usual := &order-burger.assuming( 'veggie' ); &order-burger-and-fries := &order-burger.assuming( side => 'french fries' );

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.

order-the-usual( 'salsa' ); order-the-usual( side => 'broccoli' ); order-burger-and-fries( 'plain' ); order-burger-and-fries( :type<<double-beef>> );

Önelemzés

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:

sub logarithm(Numeric $x, Numeric :$base = exp(1)) { log($x) / log($base); } my @params = &logarithm.signature.params; say @params.elems, ' parameters'; for @params { say "Name: ", .name; say " Type: ", .type; say " named? ", .named ?? 'yes' !! 'no'; say " slurpy? ", .slurpy ?? 'yes' !! 'no'; say " optional? ", .optional ?? 'yes' !! 'no'; }
2 parameters Name: $x Type: Numeric() named? no slurpy? no optional? no Name: $base Type: Numeric() named? yes slurpy? no optional? yes

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

Osztályok és objektumok

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.

1 class Task { 2 has &!callback; 3 has Task @!dependencies; 4 has Bool $.done; 5 6 method new(&callback, Task *@dependencies) { 7 return self.bless(*, :&callback, :@dependencies); 8 } 9 10 method add-dependency(Task $dependency) { 11 push @!dependencies, $dependency; 12 } 13 14 method perform() { 15 unless $!done { 16 .perform() for @!dependencies; 17 &!callback(); 18 $!done = True; 19 } 20 } 21 } 22 23 my $eat = 24 Task.new({ say 'eating dinner. NOM!' }, 25 Task.new({ say 'making dinner' }, 26 Task.new({ say 'buying food' }, 27 Task.new({ say 'making some money' }), 28 Task.new({ say 'going to the store' }) 29 ), 30 Task.new({ say 'cleaning kitchen' }) 31 ) 32 ); 33 34 $eat.perform();

Osztály

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:

1 my $obj = Int; 2 if $obj.defined { 3 say "Type object"; 4 } else { 5 say "Ordinary, defined object"; 6 }

Adattagok

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.

1 has &!callback;

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.

1 has Task @!dependencies;

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:

1 has Bool $.done;

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:

1 has Bool $!done; 2 method done() { return $!done }

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.

1 has Bool $.done is rw;

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.

Metódusok

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.

1 method add-dependency(Task $dependency) { 2 push @!dependencies, $dependency; 3 }

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.

1 method perform() { 2 unless $!done { 3 .perform() for @!dependencies; 4 &!callback(); 5 $!done = True; 6 } 7 }

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.

Konstruktorok

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:

1 method new(&callback, Task *@dependencies) { 2 return self.bless(*, :&callback, :@dependencies); 3 }

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.

Osztály példányosítása

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:

1 my $eat = Task.new({ say 'eating dinner. NOM!' });

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.

1 my $eat = 2 Task.new({ say 'eating dinner. NOM!' }, 3 Task.new({ say 'making dinner' }, 4 Task.new({ say 'buying food' }, 5 Task.new({ say 'making some money' }), 6 Task.new({ say 'going to the store' }) 7 ), 8 Task.new({ say 'cleaning kitchen' }) 9 ) 10 );

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:

1 making some money 2 going to the store 3 buying food 4 cleaning kitchen 5 making dinner 6 eating dinner. NOM!

Öröklődés

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.

1 class Employee { 2 has $.salary; 3 4 method pay() { 5 say "Here is n$$.salary"; 6 } 7 8 } 9 10 class Programmer is Employee { 11 has @.known_languages is rw; 12 has $.favorite_editor; 13 14 method code_to_solve( $problem ) { 15 say "Solving $problem using $.favorite_editor in " 16 ~ $.known_languages[0] ~ '.'; 17 } 18 }

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:

1 my $programmer = Employee.new( 2 salary => 100_000, 3 known_languages => <Perl5, Perl6, Erlang, C++>, 4 favorite_edtor => 'vim' 5 );

$programmer.code to solve(’turing problem’); $programmer.pay();

Örökölt metódusok felüldefiniálása

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.

1 class Cook is Employee { 2 has @.utensils is rw; 3 has @.cookbooks is rw; 4 5 method cook( $food ) { 6 say "Cooking $food"; 7 } 8 9 method clean_utensils { 10 say "Cleaning $_" for @.utensils; 11 } 12 } 13 14 class Baker is Cook { 15 method cook( $confection ) { 16 say "Baking a tasty $confection"; 17 } 18 } 19 20 my $cook = Cook.new( 21 utensils => (<spoon ladel knife pan>), 22 cookbooks => ('The Joy of Cooking'), 23 salary => 40000); 24 25 $cook.cook( 'pizza' ); # Cooking pizza 26 27 my $baker = Cook.new( 28 utensils => ('self cleaning oven'), 29 cookbooks => ("The Baker's Apprentice"), 30 salary => 50000); 31 32 $baker.cook('brioche'); # Baking a tasty brioche

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.

Többszörös öröklés

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.

1 class GeekCook is Programmer is Cook { 2 method new( *%params ) { 3 push( %params<cookbooks>, "Cooking for Geeks" ); 4 return self.bless(%params); 5 } 6 } 7 8 my $geek = GeekCook.new( 9 books => ('Learning Perl 6'), 10 utensils => ('blingless pot', 'knife', 'calibrated oven'), 11 favorite_editor => 'MacVim', 12 known_languages => <Perl6> 13 ); 14 15 $geek.cook('pizza'); 16 $geek.code_to_solve('P =? NP');

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.

1 my $geek = GeekCook.new( 2 books => ('Learning Perl 6'), 3 utensils => ('blingless pot', 'knife', 'calibrated oven'), 4 favorite_editor => 'MacVim', 5 languages => <Perl6> 6 ); 7 8 $geek.cook('pizza'); 9 $geek.code_to_solve('P =? NP');

Önelemzés

Ö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:

1 if $o ~~ Employee { say "It's an employee" }; 2 if $o ~~ GeekCook { say "It's a geeky cook" }; 3 say $o.WHAT; 4 say $o.perl; 5 say $o.^methods(:local).join(', ');

a kimenet:

It's an employee Programmer() Programmer.new(known_languages => ["Perl", "Python", "Pascal"], favorite_editor => "gvim", salary => "to code_to_solve, known_languages, favorite_editor

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:

1 say $o.^attributes.join(', '); 2 say $o.^parents.join(', ');

Ö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.

Szerepek

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.

1 # XXX This is VERY preliminary code and needs filling out. But it 2 # does provide opportunities to discuss runtime mixins, compile time 3 # composition, requirements and a few other bits. 4 5 my regex nick { nw+ } 6 my regex join-line { ... <nick> ... } 7 my regex message-line { $<sender>=[...] $<message>=[...] } 8 9 class IRCBot { 10 has $.bot-nick; 11 method run($server) { 12 ... 13 } 14 } 15 16 role KarmaTracking { 17 has %!karma-scores; 18 19 multi method on-message($sender, $msg where /^karma <ws> <nick>/) { 20 if %!karma-scores{$<nick>} -> $karma { 21 return $<nick> ~ " has karma $karma"; 22 } 23 else { 24 return $<nick> ~ " has neutral karma"; 25 } 26 } 27 28 multi method on-message($sender, $msg where /<nick> '++'/) { 29 %!karma-scores{$<nick>}++; 30 } 31 32 multi method on-message($sender, $msg where /<nick> '--'/) { 33 %!karma-scores{$<nick>}--; 34 } 35 } 36 37 role Oping { 38 has @!whoz-op; 39 40 multi method on-join($nick) { 41 if $nick eq any(@!whoz-op) { 42 return "/mode +o $nick"; 43 } 44 } 45 46 # I'm tempted to add another candidate here which checks any(@!whoz-op) 47 multi method on-message($sender, $msg where /^trust <ws> <nick>/) { 48 if $sender eq any(@!whoz-op) { 49 push @!whoz-op, $<nick>; 50 return "I now trust " ~ $<nick>; 51 } 52 else { 53 return "But $sender, I don't trust you"; 54 } 55 } 56 } 57 58 role AnswerToAll { 59 method process($raw-in) { 60 if $raw-in ~~ /<on-join>/ { 61 self.*on-join($<nick>); 62 } 63 elsif $raw-in ~~ /<on-message>/ { 64 self.*on-message($<sender>, $<message>) 65 } 66 } 67 } 68 69 role AnswerIfTalkedTo { 70 method bot-nick() { ... } 71 72 method process($raw-in) { 73 if $raw-in ~~ /<on-join>/ { 74 self.*on-join($<nick>); 75 } 76 elsif $raw-in ~~ /<on-message>/ -> $msg { 77 my $my-nick = self.bot-nick(); 78 if $msg<msg> ~~ /^ $my-nick ':'/ { 79 self.*on-message($msg<sender>, $msg<message>) 80 } 81 } 82 } 83 } 84 85 my %pluggables = 86 karma => KarmaTracking, 87 op => Oping; 88 89 role Plugins { 90 multi method on-message($self is rw: $sender, $msg where /^youdo <ws> (nw+)/) { 91 if %pluggables{$0} -> $plug-in { 92 $self does $plug-in; 93 return "Loaded $0"; 94 } 95 } 96 } 97 98 class AdminBot is IRCBot does KarmaTracking does Oping {} 99 class KarmaKeeper is IRCBot does KarmaTracking does AnswerToAll {} 100 class NothingBot is IRCBot does AnswerIfTalkedTo does Plugins {}

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.

Szerep

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.

Fordítás

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.

1 class MyBot is IRCBot does AnswerToAll does AnswerIfTalkedTo {}

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.

1 class MyBot is IRCBot does AnswerToAll does AnswerIfTalkedTo { 2 method process($raw-in) { 3 # Do something sensible here... 4 } 5 }

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.

Multi metódusok és kompozíciók

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.

1 class KarmaKeeper is IRCBot { 2 does AnswerToAll; 3 does KarmaTracking; 4 does Oping; 5 }
Összes jelölt hívása

A process metódus az AnswerToAll and AnswerIfTalkedTo szerepekből, egy módosított szintaxist használ metódus hívására.

1 self.*on-message($msg<sender>, $msg<message>)

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.

Kifejező követelmények

AnswerIfTalkedTo szerep egy stub-ot deklarál bot-nick metódushoz, de sosem látja el implementációval:

1 method bot-nick() { ... }

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.

Szerepek futásidejű alkalmazásai

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.

1 if %pluggables{$0} -> $plug-in { 2 $self does $plug-in; 3 return "Loaded $0"; 4 }

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.

Különbségek a fordítási időben

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.

A but operátor

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.

Altípusok

1 enum Suit <spades hearts diamonds clubs>; 2 enum Rank (2, 3, 4, 5, 6, 7, 8, 9, 10, 3 'jack', 'queen', 'king', 'ace'); 4 5 class Card { 6 has Suit $.suit; 7 has Rank $.rank; 8 9 method Str { 10 $.rank.name ~ ' of ' ~ $.suit.name; 11 } 12 } 13 14 subset PokerHand of List where { .elems == 5 && all(|$_) ~~ Card } 15 16 sub n-of-a-kind($n, @cards) { 17 for @cards>>.rank.uniq -> $rank { 18 return True if $n == grep $rank, @cards>>.rank; 19 } 20 return False; 21 } 22 23 subset Quad of PokerHand where { n-of-a-kind(4, $_) } 24 subset ThreeOfAKind of PokerHand where { n-of-a-kind(3, $_) } 25 subset OnePair of PokerHand where { n-of-a-kind(2, $_) } 26 27 subset FullHouse of PokerHand where OnePair & ThreeOfAKind; 28 29 subset Flush of PokerHand where -> @cards { [==] @cards>>.suit } 30 31 subset Straight of PokerHand where sub (@cards) { 32 my @sorted-cards = @cards.sort({ .rank }); 33 my ($head, @tail) = @sorted-cards; 34 for @tail -> $card { 35 return False if $card.rank != $head.rank + 1; 36 $head = $card; 37 } 38 return True; 39 } 40 41 subset StraightFlush of Flush where Straight; 42 43 subset TwoPair of PokerHand where sub (@cards) { 44 my $pairs = 0; 45 for @cards>>.rank.uniq -> $rank { 46 ++$pairs if 2 == grep $rank, @cards>>.rank; 47 } 48 return $pairs == 2; 49 } 50 51 sub classify(PokerHand $_) { 52 when StraightFlush { 'straight flush', 8 } 53 when Quad { 'four of a kind', 7 } 54 when FullHouse { 'full house', 6 } 55 when Flush { 'flush', 5 } 56 when Straight { 'straight', 4 } 57 when ThreeOfAKind { 'three of a kind', 3 } 58 when TwoPair { 'two pair', 2 } 59 when OnePair { 'one pair', 1 } 60 when * { 'high cards', 0 } 61 } 62 63 my @deck = map -> $suit, $rank { Card.new(:$suit, :$rank) }, 64 (Suit.pick(*) X Rank.pick(*)); 65 66 @deck .= pick(*); 67 68 my @hand1; 69 @hand1.push(@deck.shift()) for ^5; 70 my @hand2; 71 @hand2.push(@deck.shift()) for ^5; 72 73 say 'Hand 1: ', map { "nn $_" }, @hand1>>.Str; 74 say 'Hand 2: ', map { "nn $_" }, @hand2>>.Str; 75 76 my ($hand1-description, $hand1-value) = classify(@hand1); 77 my ($hand2-description, $hand2-value) = classify(@hand2); 78 79 say sprintf q[The first hand is a '%s' and the second one a '%s', so %s.], 80 $hand1-description, $hand2-description, 81 $hand1-value > $hand2-value 82 ?? 'the first hand wins' 83 !! $hand2-value > $hand1-value 84 ?? 'the second hand wins' 85 !! "the hands are of equal value"; # XXX: this is wrong

Mintaillesztés

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:

my $s = 'the quick brown fox jumped over the the lazy dog'; if $s ~~ m/ << (\w+) \W+ $0 >> / { say "Found '$0' twice in a row"; }

Egy regex legegyszerűbb esete egy konstans string. Egy ilyen regex-re illesztve a stringet, megkeresi azt:

if 'properly' ~~ m/ perl / { say "'properly' contains 'perl'"; }

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:

my $str = "I'm *very* happy"; # quoting if $str ~~ m/ '*very*' / { say 'no/' } # escaping if $str ~~ m/ \* very \* / { say 'no/' }

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:

my @words = <spell superlative openly stuff>; for @words -> $w { if $w ~~ m/ pe.l / { say "$w contains $/"; } else { say "no match for $w"; } }

Ez a következő outputot generálja:

spell contains pell superlative contains perl openly contains penl no match for stuff

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ásPé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:

if $str ~~ / <[aeiou]> / { say "'$str' contains a vowel"; } # negation with a - if $str ~~ / <-[aeiou]> / { say "'$str' contains something that's not a vowel"; }

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:

# match a, b, c, d, ..., y, z if $str ~~ / <[a..z]> / { say "'$str' contains a lower case Latin letter"; }

Hozzáadhatunk vagy elvehetünk karaktereket a karakter osztályokból a + és a - jelekkel:

if $str ~~ / <[a..z]+[0..9]> / { say "'$str' contains a letter or number"; } if $str ~~ / <[a..z]-[aeiou]> / { say "'$str' contains a consonant"; }

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:

# match a date of the form 2009-10-24: m/ \d**4 '-' \d\d '-' \d\d / # match at least three 'a's in a row: m/ a ** 3..* /

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.)

my $html = '

A paragraph

And a second one

'; if $html ~~ m/ '

' .* '

' / { say 'Matches the complete string!'; } if $html ~~ m/ '

' .*? '

' / { say 'Matches only

A 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:

my $ingredients = 'milk, flour, eggs and sugar'; # prints "milk, flour, eggs" $ingredients ~~ m/ [\w+] ** [\,\s*] / && say $/;

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.

$string ~~ m/ \d**4 '-' \d\d '-' \d\d | 'today' | 'yesterday' /

Anchorok

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.

Capture-ök

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:

my $str = 'Germany was reunited on 1990-10-03, peacefully'; if $str ~~ m/ (\d**4) \- (\d\d) \- (\d\d) / { say 'Year: ', $/[0]; say 'Month: ', $/[1]; say 'Day: ', $/[2]; # usage as an array: say $/.join('-'); # prints 1990-10-03 }

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:

my $ingredients = 'eggs, milk, sugar and flour'; if $ingredients ~~ m/(\w+) ** [\,\s*] \s* 'and' \s* (\w+)/ { say 'list: ', $/[0].join(' | '); say 'end: ', $/[1]; }

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:

my $s = 'the quick brown fox jumped over the the lazy dog'; if $s ~~ m/ << (\w+) \W+ $0 >> / { say "Found '$0' twice in a row"; }

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.

Reguláris kifejezés deklarálása

Deklarálhatunk regex-eket ugyanúgy, mint alprogramokat és el is nevezhetjük őket.

my regex word { \w+ [ \' \w+]? } my regex dup { << <word=&word>> \W+ $<word> >> } if $s ~~ m/ <dup=&dup> / { say "Found '{$<dup><word>}' twice in a row"; }

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.

Módosítók

Az előző példa szavak listájára való illesztésre:

m/(nw+) ** [n,ns*] ns* 'and' ns* (nw+)/

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:

my $ingredients = 'eggs, milk, sugar and flour'; if $ingredients ~~ m/:s ( \w+ ) ** \,'and' (\w+)/ { say 'list: ', $/[0].join(' | '); say 'end: ', $/[1]; }

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.

Visszalépéses vezérlés

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:

my regex word { :ratchet \w+ [ \' \w+]? } my regex dup { <word=&word> \W+ $<word> } # no match, doesn't match the 'and' # in 'strand' without backtracking 'strand and beach' ~~ m/<&dup>/

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.

Helyettesítés

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:

my $spacey = 'with many superfluous spaces'; say $spacey.subst(rx/ \s+ /, ' ', :g); # output: with many superfluous spaces

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 ($).

Nyelvtanok

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.

1 # file lib/JSON/Tiny/Grammar.pm 2 3 grammar JSON::Tiny::Grammar { 4 rule TOP { ^[ <object> | <array> ]$ } 5 rule object { '{' ~ '}' <pairlist> } 6 rule pairlist { [ <pair> ** [ n, ] ]? } 7 rule pair { <string> ':' <value> } 8 rule array { '[' ~ ']' [ <value> ** [ n, ] ]? } 9 10 proto token value { <...> }; 11 12 token value:sym<number> { 13 '-'? 14 [ 0 | <[1..9]> <[0..9]>* ] 15 [ n. <[0..9]>+ ]? 16 [ <[eE]> [n+|n-]? <[0..9]>+ ]? 17 } 18 19 token value:sym<true> { <sym> }; 20 token value:sym<false> { <sym> }; 21 token value:sym<null> { <sym> }; 22 token value:sym<object> { <object> }; 23 token value:sym<array> { <array> }; 24 token value:sym<string> { <string> } 25 26 token string { 27 n" ~ n" [ <str> | nn <str_escape> ]* 28 } 29 30 token str { 31 [ 32 <!before nt> 33 <!before nn> 34 <!before nn> 35 <!before n"> 36 . 37 ]+ 38 # <-["nnntnn]>+ 39 } 40 41 token str_escape { 42 <["nn/bfnrt]> | u <xdigit>**4 43 } 44 45 } 46 47 48 # test it: 49 my $tester = '{ 50 "country": "Austria", 51 "cities": [ "Wien", "Salzburg", "Innsbruck" ], 52 "population": 8353243 53 }'; 54 55 if JSON::Tiny::Grammar.parse($tester) { 56 say "It's valid JSON"; 57 } else { 58 # TODO: error reporting 59 say "Not quite..."; 60 }

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:

1 proto token value { <...> }; 2 3 token value:sym<number> { 4 '-'? 5 [ 0 | <[1..9]> <[0..9]>* ] 6 [ n. <[0..9]>+ ]? 7 [ <[eE]> [n+|n-]? <[0..9]>+ ]? 8 } 9 10 token value:sym<true> { <sym> }; 11 token value:sym<false> { <sym> };

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.

Nyelvtani öröklődés

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:

1 grammar JSON::Tiny::Grammar::WithComments 2 is JSON::Tiny::Grammar { 3 4 token ws { 5 \s* [ '//' nN* nn ]? 6 } 7 } 8 9 my $tester = '{ 10 "country": "Austria", 11 "cities": [ "Wien", "Salzburg", "Innsbruck" ], 12 "population": 8353243 // data from 2009-01 13 }'; 14 15 if JSON::Tiny::Grammar::WithComments.parse($tester) { 16 say "It's valid (modified) JSON"; 17 }

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.

1 grammar JSON::ExtendedNumeric is JSON::Tiny::Grammar { 2 token value:sym<nan> { <sym> } 3 token value:sym<inf> { <[+-]>? <sym> } 4 }

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.

Adat eltávolítása/kicsomagolása

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:

1 class JSON::Tiny::Actions { 2 method TOP($/) { make $/.values.[0].ast } 3 method object($/) { make $<pairlist>.ast.hash } 4 method pairlist($/) { make $<pair>ź.ast } 5 method pair($/) { make $<string>.ast => $<value>.ast } 6 method array($/) { make [$<value>ź.ast] } 7 method string($/) { make join '', $/.caps>>.value>>.ast } 8 9 # TODO: make that 10 # make +$/ 11 # once prefix:<+> is sufficiently polymorphic 12 method value:sym<number>($/) { make eval $/ } 13 method value:sym<string>($/) { make $<string>.ast } 14 method value:sym<true> ($/) { make Bool::True } 15 method value:sym<false> ($/) { make Bool::False } 16 method value:sym<null> ($/) { make Any } 17 method value:sym<object>($/) { make $<object>.ast } 18 method value:sym<array> ($/) { make $<array>.ast } 19 20 method str($/) { make ~$/ } 21 22 method str_escape($/) { 23 if $<xdigit> { 24 make chr(:16($<xdigit>.join)); 25 } else { 26 my %h = 'nn' => "nn", 27 'n' => "nn", 28 't' => "nt", 29 'f' => "nf", 30 'r' => "nr"; 31 make %h{$/}; 32 } 33 } 34 } 35 36 my $actions = JSON::Tiny::Actions.new(); 37 JSON::Tiny::Grammar.parse($str, :$actions);

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.

1 rule TOP { ^ [ <object> | <array> ]$ } 2 method TOP($/) { make $/.values.[0].ast }

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.

1 rule object { '{' ~ '}' <pairlist> } 2 method object($/) { make $<pairlist>.ast.hash }

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.

1 rule pairlist { [ <pair> ** [ n, ] ]? } 2 method pairlist($/) { make $<pair>ź.ast; }

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.

1 rule pair { <string> ':' <value> } 2 method pair($/) { make $<string>.ast => $<value>.ast }

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.

1 token value:sym<null> { <sym> }; 2 method value:sym<null>($/) { make Any } 3 4 token value:sym<object> { <object> }; 5 method value:sym<object>($/) { make $<object>.ast }

Ha a <value> hívás talált, az action metódus ugyanaz a szimbólum mintha egy alszabályt futtattunk volna.

Beépített típusok, operátorok és metódusok

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.

Számok

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

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

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

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.

Complex

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átorLeí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átorLeírás
+ conversion to number
- negation

Matematikai függvények és metódusok:

MetódusLeí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

Stringek

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átorLeí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

Logikai típusok

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:

Strings

Üres string vagy a "0" kiértékelése False. Minden más string kiértékelése True.

Numbers

Az összes szám kiértékelése True, kivéve a nullát.

Lists and Hashes

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.

1 my $num = 5; 2 3 # implicit boolean context 4 if $num { say "True" } 5 6 # explicit boolean context 7 my $bool = ?$num; 8 9 # negated boolean context 10 my $not_num = !$num;