Perličky: úvod do referencí

22. 2. 2008
Doba čtení: 10 minut

Sdílet

Dříve nebo později se každý programátor setká s potřebou odkazovat na data nebo kód nepřímo. Analogicky k souborovému systému, v Perlu můžeme vytvářet „měkké“ i „tvrdé“ odkazy. Obvykle se ty první nazývají symbolické reference a ty druhé pouze reference. Dnes nás čekají ty tvrdé, tj. reference.

Reference na existující objekty

Perlovské reference dědí po pointrech z nízkoúrovňových jazyků nutnost syntaktického značení aktu reference i dereference. Z referencí v jiných jazycích si pak bereme nemožnost ukazovat referencí do temné oblasti paměti pod nadvládou core dumpů. Reference je skalární objekt a může ukazovat prakticky na jakýkoliv objekt. (Dokonce i na sebe sama, ale to má, kromě toho, že je to kuriozita, pramalé využití.) Pro vytvoření reference na již existující objekt použijeme operátor \.

$r = \$a;       # odkaz na skalár
$r = \@a;       # odkaz na pole
$r = \%a;       # odkaz na hash
$r = \&a;       # odkaz na funkci (všimněte si znaku &)
$r = \*a;       # odkaz na glob
            # (pokud nevíte, co to je, nelamte si hlavu,
            # probereme to v příštích dílech)

Pro každý objekt Perl počítá počet referencí, které na něj odkazují. Všem zmíněným případům je společné to, že se u daného objektu zvýšil počet referencí. Počet referencí se sníží, pokud zanikne platnost identifikátoru samotné proměnné nebo reference na ni. Vlastní data se však zlikvidují až ve chvíli, kdy se počet referencí sníží na nulu. Nemusíme se tedy bát, že bychom referencí ukazovali na již neexistující data.

{
    my $r;
    {
        my $a = "Jsem řetězec";
        $r = \$a;
        # řetězec je přístupný přes $a i přes $r
    }
    # v tomto místě stále můžeme přes $r vidět na řetězec
}
# ale tady už ne

Jednoduché dereferencování

S daty, na které ukazuje reference, můžeme normálně pracovat tak, že dáme proměnnou typu reference tam, kde bychom normálně psali identifikátor obyčejné proměnné.

$a = "Michael Knight";      # proměnná pro přímý přístup k datům
$r = \$a;           # reference

print $a;           # vypíše řetězec
print $r;           # nevypíše řetězec, ale info o referenci, něco jako "SCALAR(0x604a80)"
print ${$r};            # vypíše referencovaná data (řetězec)

${$r} .= "and Devon Miles"; # s těmito daty můžeme normálně pracovat, přitom samozřejmě měníme ${$r} i $a
print $a;           # Michael Knight and Devon Miles

Podobný přístup máme i při dereferencování odkazů na další typy objektů.

@pole = qw/Michael Devon Bonnie/;
$r = \@pole;

print ${$r}[1];             # Devon
@serazeno = sort @{$r};         # qw/Bonnie Devon Michael/

$r = \%ENV;
chdir ${$r}{HOME};          # přesun do domovského adresáře

print @{$r}{qw/HOME PATH/};     # z dereferencovaných hashů a polí lze stále vytvářet řezy
                    # a vůbec normálně pracovat (zmínil jsem to už?)

sub funkce { print "funkce" }
$r = \&funkce;
&{$r};                  # zavolá funkci

Psaní složených závorek kolem reference lze vypustit, je-li tato reprezentována pouze samotnou skalární proměnnou. Lze tedy psát $$r místo ${$r}$$r[1] místo ${$r}[1]. Přesněji řečeno, složené závorky jsou interpretovány jako blok kódu, a tudíž do nich lze zapsat cokoliv, co se nakonec vyhodnotí jako reference. Další syntaktickou úlevou je šipkový operátor ->. Tento lze použít, pokud se chceme dostat k jednomu prvku pole nebo hashe, či použít volání funkce. Na levé straně operátoru -> může opět stát cokoliv, co vydá za referenci na hash, pole nebo funkci. Speciálně užitečné to je v případě, že takovou referenci na získáme jako návratovou hodnotu z funkce.

$r = \@pole;
print $r->[1];

$r = \%ENV;
chdir $r->{HOME};

# pokud chceme víc indexů (řez polem nebo hashem), šipku použít nelze,
# tedy takovýto kód neprojde
# print @r->{qw/HOME PATH/};

$r = \&funkce;
$r->();

# dereferencovat lze cokoliv co je v konečném důsledku reference
print ${ my $r2 = \%ENV; $r2; }{HOME};
${ print "ahoj "; $r; }();

# šipku lze použít i s návratovou hodnotou funkce
sub vrat_referenci { return \%ENV; }

print vrat_referenci()->{PATH};

V Perlu není možné zaměňovat typy referencí, tj. například odkaz na hash dereferencovat jak pole nebo skalár. (Pokud se o to pokusíme, Perl nás ošklivě pokouše.) Chceme-li vědět, na co proměnná odkazuje (případně jestli vůbec), použijeme operátor ref. Ten vrací typ reference svého argumentu, případně prázdný řetězec.

@a = (\@ARGV, \%ENV, \*STDIN, \&time, 42);
for (@a) {
    print ref $_ ? ref $_ : "no ref", "\n";
}

# kompaktnější zápis cyklu
print (ref $_ || "no ref", "\n") for @a;

V posledním příkladě stojí za povšimnutí odkaz \&time. Není to, jak by se mohlo zdát, odkaz na zabudovanou funkci time, ale odkaz na uživatelskou funkci time, kterou někde musíme dodefinovat, jinak pokus o případné dereferencování selže. Dodefinování můžeme provést například takto: sub time { time }, což je volání zabudovaného time v naší funkci se shodným názvem. Kdybychom definovali sub time { &time }, program se zacyklí.

Reference na anonymní objekty

Často je potřeba vytvořit referenci přímo na nějaká data, aniž bychom se při tom chtěli zatěžovat vytvářením dočasné proměnné. Pokud jsou data skalárního typu, můžeme opět využít operátor \.

$cislo = \2000;     # odkaz na číslo
$jmeno = \"Kitt";   # odkaz na řetězec

$odkaz = \\"Karr";  # odkaz na odkaz na řetězec

$cas = \time;       # odkaz na číslo, které jsme získali jako návratovou hodnotu z funkce
            # (zde není symbol &)

Pokus o vytvoření odkazu na seznam touto metodou ale selže, jelikož \ je vůči seznamu distributivní. Vznikne tak seznam odkazů a nikoliv odkaz na seznam. (Ještě větší škodu napácháme, pokud se pokusíme vzniklý seznam přiřadit do skalární proměnné.) K vytvoření odkazu na anonymní pole se používají hranaté závorky []. Odkaz na anonymní hash tvoří složené závorky {}. V obou případech se vytváří skalární objekt (ta reference). Složené závorky anonymního odkazu na hash se občas lidem (a občas také Perlu) pletou se složenými závorkami obsahujícími blok kódu. Nicméně to je život, a pokud nebudete dělat vylomeniny, obě strany to pochopí dobře.

\(3, 4, 5)      # to samé co (\3, \4, \5)

# příklad výše lze ješte více zjednodušit takto
print (ref $_ || "no ref", "\n") for \(@ARGV, %ENV, *STDIN, &time), 42;

$r = [3, 4, 5];     # odkaz na anonymní pole
print $_ for @$r;   # 345

$r = [qw/Kitt Karr/];   # uvnitř [] lze mít vnořené seznamy, které se rozbalí, podobně jako u ()

@a = ([3], [4], [5]);   # nicméně toto je pole odkazů na pole (tj. „dvourozměrné“ pole)
$r = [[3], [4], [5]];   # a toto je odkaz na anonymní takové pole

# reference na anonymní hash
$r = {
    "Michael" => "Knight",
    "Devon" => "Miles"
}

# příklad, kdy se může plést {} jako blok a jako reference na
# anonymní hash
# pokud chceme použít blok na levé straně šipky, musíme Perlu trochu
# pomoct klíčovým slovem do
print do { print "cesta: "; \%ENV }->{PATH};

# srovnejte s
print { "Michael" => "Knight" }->{Michael};

Odkaz na anonymní funkci vytvoříme pomocí klíčového slova sub, za kterým není identifikátor, ale přímo blok kódu.

$r = sub { print "Jsem anonymní funkce\n" };
&$r;
$r->();

Closures (uzávěry)

Reference na anonymní funkce mají jednu zajímavou vlastnost. Pokud v těle funkce používáme proměnnou, je tato dosažitelná, kdykoliv je funkce volána, a to i pokud má podle normálního toku programu být již zaniklá. Stane-li se tak, jsou data proměnné dosažitelná pouze z vnitřku této anonymní funkce, a tedy „uzavřena“před okolím. Tuto vlastnost lze využít k mnoha účelům, například k jednoduché implementaci „private“ proměnných bez nutnosti nasazování mašinérie objektového programování.

sub privatni_promenna {
    my $prom = "Normal Cruise";
    return sub {
        my $n = shift;
        $prom = $n if defined $n;
        return $prom;
    }
}

$p1 = privatni_promenna;
print &$p1;         # Normal Cruise
&$p1("Auto Cruise");
print &$p1;         # Auto Cruise

$p2 = privatni_promenna;
print &$p2;         # Normal Cruise
$p2->("Pursuit");        # lze také zavolat šipkou
print &$p2;         # Pursuit
print &$p1;         # stále Auto Cruise, každá funkce má svou "prom"

Toto „kouzlo“ si možná zaslouží podrobnější výklad. Funkce privatni_promenna definuje lokální proměnnou $prom a vrací odkaz na anonymní funkci. Tato anonymní funkce je klasická funkce typu get-set. Podle toho, zda máme nebo nemáme parametr, buď hodnotu $prom nastavíme, nebo pouze vrátíme. Identifikátor $prom končí svou platnost na konci funkce

privatni_promenna. Ale tím, že máme v  $p1
odkaz na funkci, která s  $prom pracuje, zůstává
hodnota $prom nadále v paměti (ale není jinak
dosažitelná než přes volání &$p1). Při vytvoření
$p2 voláme opět funkci  privatni_promenna

a historie se opakuje, nicméně jelikož jde o úplně jiné volání, máme i úplně jinou lokální proměnnou $prom, a tudíž funkce volané přes &$p1&$p2 pracují s nezávislými hodnotami.

Autovivification

Další zajímavou vlastností referencí je autovivification (trochu se bojím to překládat do češtiny, zůstaneme tedy u anglického názvu). Pokud při psaní kódu použijeme proměnnou, kterou jsme ještě nedeklarovali, Perl ji za nás vytvoří a inicializuje. Podobné je to s referencemi, chceme-li dereferencovat proměnnou, která není referencí, Perl automaticky příslušná data vytvoří, proměnnou prohlásí za referenci na tato data a tváří se nevinně. Tato vlastnost se hodí zejména v případě složitějších datových struktur, například jsou-li reference obsaženy v hashi a nám se nechce kontrolovat, zda nějaký klíč byl nebo nebyl již inicializován, když ho dereferencujeme.

my $a;

$$a = 5;
print $a;       # SCALAR(…)
print $$a;      # 5

undef $a;
@$a = 0..9;
print $a;       # ARRAY(…)
print scalar @$a;   # 10

Konstrukce složitých datových struktur

Úspěšné zvládnutí referencí je branou ke konstrukci datových struktur složitějších, než jsou pole a hashe. Tyto se právě konstruují pomocí umístění referencí do příslušné primitivní struktury. Jelikož reference můžou odkazovat i na bloky kódu, lze v těchto strukturách uchovávat nejen data, ale i chování programu. Při deklaraci obsahu složitějších datových struktur hojně používáme reference na anonymní objekty.

# pole hashů
@ucastnici_zajezdu = (
    {
        jmeno => "František Koudelka",
        oddil => "STS Chvojkovice-Brod"
    }, {
        jmeno => "MacGyver",
        oddil => "Phoenix Foundation"
    }
);

# pole polí 8x8
@matice = map { [1 .. 8] } (1 .. 8);

# hash funkcí
%projevy = (
    "pes" => sub { print "haf" },
    "prase" => sub { print "kvík" },
);

# něco ještě složitějšího
$menu = {
    "File" => {
        "Open" => \&file_open,
        "Save" => \&file_save,
        "Quit" => sub { exit(0); }
    },
    "Help" => \&help
};

Ve všech případech jsme použili reference, ať na anonymní, nebo pojmenované objekty uvnitř polí a hashů. Chceme-li takovouto strukturu procházet, použijeme princip dereference opakovaně.

print $ucastnici_zajezdu[1]->{jmeno};    # MacGyver
$menu->{File}->{Quit}->();     # konec programu

%mac = %{$ucastnici_zajezdu[1]};    # celý hash; zde nemůžeme vynechat {}, jelikož
                    # reference není jednoduchá skalární proměnná, ale index v poli

Mnohonásobné dereferencování lze ještě o píď zjednodušit. Platí totiž pravidlo, že šipku -> můžeme vynechat mezi dvěma závorkovými věcmi (což jsou indexy nebo volání funkce). Lze tedy psát $ucastnici_zajezdu[1]{jmeno}, $matice[3][5]$menu->{File}{Quit}(). První šipku za $menu ale odstranit nemůžeme, neboť tato není mezi závorkovými věcmi. Pak by to totiž znamenalo, že nedereferencujeme $menu, ale indexujeme %menu, což je něco úplně jiného.

Cyklické reference

Na začátku článku bylo řečeno, že Perl uchovává počet referencí na každý objekt a objekt zlikviduje, až když je počet referencí nula. To funguje dobře, pokud graf referencí tvoří stromovou strukturu. Problém může nastat, pokud vytvoříme reference, které na sebe přímo či nepřímo navzájem ukazují. V tomto případě počítání referencí selže a objekty zůstanou v paměti i poté, co k nim z programu neexistuje žádný přístup. Tyto objekty budou mít stále započtenu jednu referenci (nebo více) skrze ukazování na sebe sama. Pomocí funkce weaken z balíku Scalar::Util můžeme referenci „zeslabit“, tj. reference se nebude u cílového objektu započítávat.

bitcoin_skoleni

Použití slabých referencí má dva praktické dopady a jedno varování. Pokud je použijeme správně u cyklických datových struktur, zajistíme si destrukci objektu ve správnou chvíli. Na druhou stranu, budeme-li používat slabou referenci pro normální práci s objektem, nebude tato slabá reference schopna objekt udržet v paměti sama o sobě. Jakmile zaniknou všechny silné (normální) reference, objekt bude zlikvidován a slabá reference nabude hodnoty undef. Nakonec to varování: kopírujeme-li slabou referenci, výsledkem je silná reference. (Pokud chceme slabou, musíme ji znovu a zvlášť oslabit.) Chceme-li zjistit sílu reference, můžeme použít funkci isweak ze stejného balíku.

{
    my ($a, $b);
    ($a, $b) = \($b, $a);
    # reference na sebe nyní křížově odkazují
}
# zde už nemáme k dispozici proměnné $a a $b, ale
# v paměti kvůli cyklu zůstanou

use Scalar::Util qw/weaken/;
{
    my ($a, $b);
    ($a, $b) = \($b, $a);
    weaken $a;
    # reference na sebe nyní křížově odkazují, ale počet referencí je
    # $a: 2 (jednou za $a a jednou za $b)
    # $b: 1 (pouze jednou za $b; $a se nepočítá)
}
# zde přestanou platit $a i $b, počet referencí tedy bude
# $a: 1, $b: 0; $b se zlikviduje, čímž bude i $a: 0 a zlikviduje se také

Pokračování příště!

Článek o referencích poněkud nabobtnal, rozhodl jsem se jej tedy rozdělit na více částí. Na konci první části máme za sebou téměř kompletní teorii silných referencí, příště se tedy podíváme na něco více praktického.

Autor článku