Hlavní navigace

Perličky: prototypy

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

Sdílet

Prototypy funkcí v jazyce Perl jsou další z pokročilých technik, umožňující produkci omezeného množství magie. Jejich použití je poněkud nebezpečné v běžném, „spořádaném“ programu, nicméně v zákulisí různých modulů se to jimi jen hemží, zejména pokud příslušný modul vytváří syntaktické doplňky jazyka.

Typy parametrů funkce

Klasická definice podprogramu v Perlu uvažuje pouze s jedním parametrem typu pole. Všechny parametry jsou vyhodnoceny v seznamovém kontextu a spojeny do jednoho pole @_.

sub funkce { … }

funkce(2);          # parametrem funkce je pole (2)
funkce + 2;         # tady také

funkce(@a, @b);         # parametrem je spojené pole (@a, @b)
funkce(%h);         # parametrem je pole vzniklé z %h

Pomocí prototypování funkcí máme do jisté míry možnost určit, jaký typ parametru je funkcí očekáván. Tento prototyp nelze ale chápat jako kontrolu na typ či počet parametrů (ta je velmi omezená a lze ji řešit jinak, viz díl o konstruktorech tříd), ale jako vynucení daného typu či kontextu. Velké využití prototypů je při tvorbě funkcí, které svým chováním imitují některou vestavěnou funkci či operátor.

Skalární prototyp

Následující funkce očekává jeden skalární parametr:

sub funkce ($) { return 0.5 * $_[0]; }

print funkce(2);        # vypíše samozřejmě 1

print funkce(2, 3, 4);      # Chyba: too many arguments …

Zajímavější použití této funkce je jako unární operátor.

print funkce 2;         # vypíše 1

print funkce 2, 3, 4;       # vypíše 134, chová se jako
print funkce(2), 3, 4;      # (srovnejte s funkcí bez prototypu)

my @a = (2, 3, 4);
print funkce @a;        # vypíše 1.5 (0.5*3), chová se jako
print funkce(scalar(@a));

print funkce @a, @a;        # vypíše 1.5234

Podobně lze definovat funkci, která očekává více skalárních parametrů.

sub funkce ($$) { return $_[0] + $_[1]; }

print funkce 2, 3, 4;       # toto nyní nebude fungovat,
                # protože funkce ztratila výsadu
                # unárního operátoru

print funkce @a;        # toto také ne, not enough arguments …
print funkce @a, @a;        # vypíše 6 (3+3, aneb scalar(@a) + scalar(@a))

Nepovinné parametry

Pomocí znaku středník specifikujeme hranici mezi povinnými a nepovinnými parametry.

sub funkce ($;$) { return $_[0] + ($_[1] // 0); }

Překvapuje-li vás operátor //, jedná se o takzvaný defined-or, dostupný od verze Perlu 5.10. Výraz a // b se chová ve smyslu defined a ? a : b. Volání naší funkce bude mít následující sémantiku:

print funkce 2, 3, 4;       # opět nejde, příliš mnoho parametrů
print funkce 2, 3;      # chová se jako volání se dvěma
                # parametry, tedy žravě

print funkce @a;        # volání s jedním parametrem je
                # v pořádku, druhý je nepovinný

print funkce @a, @a;        # oba parametry jsou vyhodnoceny ve
                # skalárním kontextu

Implicitní parametr

Od verze Perlu 5.10 lze poslední nebo poslední povinný skalární parametr automaticky doplnit z proměnné $_. Lze tak vytvářet funkce sémanticky podobné například funkci lc.

sub funkce ($_) { return $_[0] + ($_[1] // 0); }

$_ = 9;
print funkce 1;         # 10

print funkce 1, 1;      # 2

Pokud bychom využili varianty sub funkce (_;$), nedočkáme se doplnění parametru z  $_ v případě jednoparametrového volání, ale pouze v případě volání bez parametrů. Tento prototyp je potřeba chápat tak, že funkce požaduje alespoň jeden parametr a volitelně dva. Pokud není dodán žádný, doplňuje se první parametr z  $_. Pokud je dodán jeden, volá se funkce právě s tímto jedním parametrem a druhý se nedoplňuje. (Na rozdíl od prototypu ($_), který říká: funkce má vždy dva parametry a druhý se může doplnit z proměnné $_.)

Seznamový prototyp

Seznamový prototyp pro pole @ nebo hashe % se používá pro variadické funkce, tj. funkce s libovolným počtem parametrů. Funkce s prototypem (@) má stejnou sémantiku volání jako klasická funkce bez jakéhokoliv prototypu. Jelikož seznamový prototyp pohltí všechny zbývající parametry, nemají smysl kombinace typu (@@) nebo (@%). Rozdíl

mezi @% není známý, %

dokonce ani nekontroluje sudý počet parametrů.

Před seznamovým znakem je však možno použít další znaky, například skalární. Funkce s prototypem ($@) bude očekávat jeden skalární a jeden seznamový parametr.

Prototyp pro kód

Funkce se znakem & ve svém prototypu bude očekávat jako parametr anonymní funkci. Pokud je tento znak na prvním místě, můžeme vynechat slovo sub a čárku za parametrem. Tímto způsobem lze imitovat operátory jako sort nebo map.

sub funkce (&@) { my $kod = shift; for $_ (@_) { &$kod } }

my @a = (2,3,4);

# vypíše 345
funkce {
        print $_ + 1;
} @a;

Použití funkce s prototypem pro kód je také jeden z triků, kterak vytvořit nové syntaktické konstrukce v Perlu. Klasickým příkladem je blok  try-catch-finally.

sub try (&@) {
    my ($try, $catch, $finally) = @_;
    eval { &$try };
    if ($@) {
        local $_ = $@;
        &$catch;
    }
    &$finally if defined $finally;
}

sub catch (&@) { return @_; }
sub finally (&) { return shift; }

try {
    print "ahoj\n";
    die "ahoj";
} catch {
    print "chycena vyjimka $_";
} finally {
    print "blok finally\n";
};

# obdobně bez finally
try {
    die "vyjimka";
} catch {
    print "chycena vyjimka $_";
};

Při úvahách, jak tato magie funguje, je potřeba představit si volání uvedených funkcí takto:

try (
    sub { … },
    catch (
        sub { … },
        finally ( sub { … } )
    )
);

Prototyp pro typegloby

Znakem * specifikujeme očekávaný parametr typu typeglob, slovo bez předpony a další. Pomocí tohoto typu parametru můžeme například imitovat funkci open nebo použít další triky se symbolickými referencemi.

sub otevri_pro_zapis (*$) { no strict 'refs'; return open shift, ">", shift; }

# lze použít obě varianty otevření souboru
otevri_pro_zapis(SOUBOR, 'a.txt');
otevri_pro_zapis(my $soubor, 'b.txt');

# tento příklad je trochu umělý, ale pro demonstraci stačí
sub vyrob_print_funkci ($*$) {
    my ($pred, $jmeno, $za) = @_;
    my $funkce = sub { print $pred, @_, $za };

    { no strict 'refs'; *{$jmeno} = $funkce; }
}

vyrob_print_funkci '[', hranaty_print, ']';

hranaty_print('ahoj');      # [ahoj]

Prototyp pro odkazy

Pomocí znaku \ před znakem typu požadujeme vyhodnocení příslušného parametru jako referenci, což lze s výhodou použít při imitaci funkcí, jako je například push.

sub imitace_push (\@@) {
    my $pole = shift;
    @$pole = (@$pole, @_);
}

my @a = (2,3,4);

imitace_push @a, 5, 6, 7;
print @a;           # 234567

Všimněme si, že při volání imitace_push je automaticky první parameter převeden na referenci. Podobným způsobem lze specifikovat referenci na další typy. Pomocí hranatých závorek můžeme uvést i alternativní typy referencí. Lze tak vytvořit například„chytrý push“:

sub chytry_push (\[%@]@) {
    my $r = shift;
    if (ref $r eq "ARRAY") {
        @$r = (@$r, @_);
    } else {
        %$r = (%$r, @_);
    }
}

my @a = (2,3,4);
my %b = (2 => 3);

chytry_push @a, 5, 6, 7;
chytry_push %b, 4 => 5, 6 => 7;

Prázdný prototyp

V prototypu funkce je možné explicitně uvést absenci parametrů. Například vestavěná funkce time má takovýto prototyp.

sub funkce () { return 1; }

print funkce + 2;       # vytiskne 3, pokud bychom neuvedli
                # prototyp, bylo by toto volání chápáno
                # jako print funkce(+2);

Prototypy existujících funkcí

Prototyp již existující funkce lze zjistit pomocí funkce prototype. Chceme-li zjistit prototyp vestavěné funkce Perlu, přidáme před název funkce slovo CORE::. Některé vestavěné funkce, například print, však nelze vyjádřit prototypem. Takovou vestavěnou funkci pak nelze nahradit vlastní implementací.

bitcoin_skoleni

my $f = 'CORE::' . shift;
print 'sub ', $f, ' (', prototype($f), ") {...}\n";

# prototypy některých vestavěných funkcí:
sub CORE::each (\%) {...}
sub CORE::lc (_) {...}
sub CORE::listen (*$) {...}
sub CORE::mkdir (_;$) {...}
sub CORE::open (*;$@) {...}
sub CORE::push (\@@) {...}
sub CORE::sleep (;$) {...}
sub CORE::stat (*) {...}
sub CORE::syswrite (*$;$$) {...}
sub CORE::time () {...}
sub CORE::warn (@) {...}

Závěr

Prototypy funkcí jsou jednou z mocných zbraní Perlu. Hodí se zejména k imitaci vestavěných operátorů a funkcí, k vytváření nových syntaktických konstrukcí a další magii, kterou obvykle používají tvůrci modulů. Věčného utrpení a zatracení se však dočká ten, kdo s prototypy nebude zacházet opatrně, případně nabude dojmu, že prototypy slouží k typové kontrole parametrů.

Použité příklady ke stažení:

  1. Unární operátor
  2. Nepovinné parametry
  3. Operátor typu map
  4. Try-catch-finally
  5. Globy
  6. Chytrý push
  7. Zjišťování prototypu

Autor článku