Hlavní navigace

Perličky: symbolické reference, typegloby

23. 5. 2008
Doba čtení: 5 minut

Sdílet

Po několika dílech programování perlích objektů „na vyšší úrovni“ a správných programovacích praktik se dnes ponoříme do zákoutí symbolických referencí a typeglobů v Perlu. Čekají nás tak první kroky směrem k temné straně, bez které by Perl nebyl tím správným Perlem – manipulace s tabulkou symbolů.

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 @%. 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 NAMEPACKAGE 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ů.

root_podpora

*{' ' 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.

Byl pro vás článek přínosný?