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.