Symbolické odkazy
Symbolické odkazy jsou zřejmě prvním logickým krůčkem od začátečnického programování k programování více pokročilému. Ačkoliv, jak již víme, jsou symbolické reference poněkud nebezpečné a místo toho se používají reference normální (tedy hard – tvrdé). Jejich použití je proto běžně zakázáno, tedy používáme-li use strict
.
V některých případech se ale symbolické reference velmi hodí. Jejich základní princip spočívá v tom, že použijeme řetězec jako jméno proměnné. Obvykle není vhodné vypínat kvůli takovýmto operacím strict
úplně, pouze pro omezený blok použít no strict
'refs'
.
use strict; use warnings; our $prom = 'smazak'; { no strict 'refs'; my $nazev = 'prom'; ${$nazev} .= ' a hranolky'; # totéž co $prom .= … } print $prom;
Všimněme si, že syntaxe použití symbolických referencí je stejná jako u obyčejných, pouze dereferencovaná proměnná neobsahuje referenci ale řetězec. Podobně můžeme symbolicky referencovat pole a hashe za použití správných předpon @
a %
. Vzniklé nebezpečí tkví v použití symbolické reference na lexikální či neexistující proměnnou – pokud bychom místo our
použili my
, v tichosti by se stalo něco jiného, než jsme zamýšleli.
Tabulka symbolů
Globální proměnné v Perlu žijí ve společné harmonii v tabulce symbolů. Tato tabulka je zevnitř programu přístupná přes speciální notaci %Package::
. (Proměnné jsou globální vždy jen v rámci nějakého modulu.) Toto nejen vypadá jako hash, ale do jisté míry se tak i chová. Hlavní balík %main::
je také známý pod zkratkou %::
. Následující prográmek nám umožní vypsat kompletní tabulku symbolů nějakého modulu.
my $pkg = shift; for my $key (sort keys %{$pkg}) { my $val = ${$pkg}{$key}; print "[$key] [$val]\n"; }
Tento výpis obsahuje mimo spousty neznámých také několik známých jmen, například _
nebo /
. Tyto odpovídají stejnojmenným proměnným $_
či $/
. Užitečné jsou pouze klíče hashe. Hodnoty, jak z výpisu patrno, pouze obsahují totéž plus jméno balíku plus hvězdičku na začátku.
Několik prvních řádků výpisu může vypadat poněkud divně:
] [*main:] [] [*main::] [] [*main::]
Toto jsou ve skutečnosti proměnné $^H
, $^X
a podobně, jejichž název sestává z příslušného kontrolního znaku (uvnitř Perlu je ale můžeme nazývat také pomocí stříšky a písmenka).
Typegloby
Je-li v tabulce symbolů pouze seznam názvů, jak může Perl vědět, jakého typu proměnná je? Odpověď na tuto otázku poskytují typegloby. Každý záznam v tabulce symbolů je typeglob příslušné proměnné. Typegloby mají předponu *
a reprezentují proměnné všech typů stejného názvu, jaký má typeglob.
Pro jednotlivé typy proměnných má typeglob takzvané sloty. Tyto sloty pak odkazují (ve smyslu tvrdých referencí) na příslušnou proměnnou i s její předponou.
$prom = 'tatarka'; print *prom{SCALAR}, qq/\n/; # SCALAR(0x…) print ${*prom{SCALAR}}, qq/\n/; # tatarka
Známé typy slotů jsou:
*prom{SCALAR} == \$prom; *prom{ARRAY} == \@prom; *prom{HASH} == \%prom; *prom{CODE} == \&prom; *prom{GLOB} == \*prom; *prom{IO} # reference na IO objekt (např. STDIN) *prom{FORMAT} # reference na formát (viz perldoc -f format) *prom{NAME} # jméno proměnné ('prom') *prom{PACKAGE} # modul proměnné ('main')
Program na výpis tabulky symbolů pomocí typeglobů jednoduše rozšíříme tak, aby u každého názvu zobrazil typ proměnné.
my $pkg = shift; for my $jmeno (sort keys %{$pkg}) { print "[$jmeno]"; for my $typ (qw/SCALAR ARRAY HASH CODE IO GLOB FORMAT/) { print q/ /, $typ if defined *{$jmeno}{$typ}; } print qq/\n/; }
Například u názvu _
je vidět, že jeho typeglob *_
obsahuje sloty jak pro pole @_
, tak pro skalár $_
. Každý typeglob obsahuje referenci alespoň ve slotu GLOB
, a to sám na sebe. Je tedy možné psát zrůdnosti typu $a=qr//;print ${*{*a{GLOB}}{SCALAR}};
.
Sloty NAME
a PACKAGE
se hodí ve chvíli, kdy zpracováváme typeglob jako parametr funkce či referenci. Umožňují nám zjistit, „odkud se typeglob vzal“.
sub tg_info { my $tg = shift; print '[', *{$tg}{PACKAGE}, '::', *{$tg}{NAME}, ']'; for my $typ (qw/SCALAR ARRAY HASH CODE IO GLOB FORMAT/) { print q/ /, $typ if defined *{$tg}{$typ}; } print qq/\n/; } $prom = 'salat'; # následující volání dělají více-méně totéž # výstup je '[main::prom] SCALAR GLOB' tg_info(*prom); tg_info('prom'); tg_info(\*prom); # následující ale vypíše informaci o proměnné salat tg_info($prom);
Manipulace s typegloby a tabulkou symbolů
Typegloby lze navzájem přiřazovat. Tímto vytváříme nikoliv duplikáty proměnných, ale jejich aliasy. Alias proměnné pak pod jiným názvem zpřístupňuje tatáž data. Podobně to dělají oba druhy referencí, ale u aliasů nemusíme provádět žádné explicitní dereference.
$prom = 'kecup'; *alias = *prom; $alias = "k humrovi $alias?\n"; print $prom; # vypíše změněný řetězec
Při přiřazování typeglobů se automaticky přiřadí všechny sloty, které zdrojový typeglob obsahuje. Například po *alias = *_
se můžeme odvolávat jak na $alias
, tak na @alias
. Chceme-li přiřadit pouze specifický slot, přiřadíme tvrdou referenci na objekt, a to buď klasicky, a nebo výběrem ze slotu zdrojového typeglobu.
$prom = 'kecup'; *alias = *prom{SCALAR}; $alias = "k humrovi $alias?\n"; print $prom; *dalsi_alias = \$prom; $dalsi_alias = "takhle by to melo vetsi smrnc.\n"; print $prom;
Podobně lze „zhmotnit“ anonymní podprogram nebo proměnnou.
sub vyrob_citac { my $val = 0; return sub { return $val++; } } my $citac = vyrob_citac(); print $citac->(); *citac = $citac; print citac();
Kombinací typeglobů a symbolických referencí dostáváme do ruky možnost, jak zavést jakoukoliv globální proměnnou jakéhokoliv typu do jakéhokoliv modulu. Na tomto principu je mimo jiné založen „import“ symbolů z použitých modulů. Například pokud použijeme use POSIX qw/ctime/
, obdržíme v našem modulu (např. main
) alias ctime
na POSIX::ctime
. O vytváření těchto aliasů se obvykle stará modul Exporter
, a ono qw/time/
, případně další, jsou parametry exportovací funkce v tomto modulu.
Z druhého konce (z modulu main
) si tento proces můžeme nasimulovat takto:
require POSIX; for my $nazev (qw/ctime mktime/) { *{$nazev} = *{"POSIX::$nazev"}{CODE}; } print ctime(mktime(8, 14, 3, 19, 1, 138));
Na druhou stranu, je možné toto používat, až zneužívat, k instalování jednoduchých wrapperů kolem funkcí.
use POSIX qw/ctime/; sub moje_ctime { print "ctime zavolano s parametry: [@_]\n"; goto &{$_ctime_backup}; } $_ctime_backup = *{'ctime'}{CODE}; *{'ctime'} = \&moje_ctime; print ctime(time);
Nebo k instalování proměnných šílených názvů.
*{' ' x 3} = sub { print "ahoj"; }; #' '(); # takhle ne, *{' ' x 3}{CODE}->(); # takhle.
Konečně, mazat položky z tabulky symbolů můžeme pomocí operátoru delete
.
delete $main::{'prom'}; # a je po jídle
Závěr
Pomocí typeglobů a symbolických referencí lze dělat poměrně pokročilou magii s globálními proměnnými. Jako bonus doporučuji shlédnout dokumentaci k modulu PadWalker
, který umožňuje nimrat se jak v lexikálních proměnných, tak v proměnných uzavřených uzávěry.