Hlavní navigace

Perličky (5)

1. 11. 2002
Doba čtení: 7 minut

Sdílet

Snad žádný větší problém se nedá řešit metodou "shora dolů". Programátor je nucen problém dekomponovat na dílčí úlohy. V tzv. imperativním paradigmatu programování (které je Perlu nejbližší) představují dílčí úlohy funkce. Dnes se budeme bavit právě o nich. Nejprve se ale podíváme na konferenci YAPS::Europe::2002.

Novinky

Že vyšla verze 5.8, už asi všichni víte. Já bych se jen krátce zmínil o tom, co přináší nového. Mezi tři základní věci patří propracovanější podpora vláken, přepracovaná vrstva vstupu a výstupu a konečně definitivní podpora UNICODE, což je asi v našich zemích nejzajímavější novinkou. Nová verze také odstraňuje spoustu chyb a přidává další, jak už to ve světě softwaru bývá.

YAPS::Europe::2002
Na konferenci YAPS::Europe::2002, která se konala 18. – 20. září v Mnichově, se vydal můj kolega. Program byl opět nabitý zajímavými přednáškami, dostavil se i Larry Wall a Damian Conway. Kolega mi věnoval trochu času a zodpověděl pár otázek:

Jak to všechno probíhalo?

Začínalo to ve středu v devět na mnichovské univerzitě. Program byl dost nahuštěný a přednášky probíhaly paralelně, takže se to nedalo všechno stihnout a člověk si musel vybírat. Trvalo to až do pátku, kdy jsme se vydali autem domů.

Jaké byly pro tebe nejzajímavější přednášky?

Asi nejzajímavější byla určitě megalomanská přednáška Conwaye o šestce a také o extrémním programování. Také jsem si nemohl nechat ujít Larryho. Měl tam jen takovou půlhodinovou úvahu, ale bylo to nesmírně zajímavé. Trošku mě zklamala přednáška o Embperlu 2.0, na kterou nepřijel Richter a kde jsem se nic nového nedozvěděl.

Nějaká veselá historka z natáčení?

Na závěr se konala dražba různých věcí, například suvenýry s podpisem Larryho a tak podobně. Nakonec, když už nebylo co dražit, američan Michael Schwerm dal do dražby svoje kalhoty…

Jak to dopadlo?

Vydražily se spolu s dalším jeho oblečením asi za 175 dolarů. Sednou mi (smích). Ne, teď si dělám srandu. Koupil je nějakej chlápek.

Co pěkného sis dovezl?

Kromě příjemných zážitků a mnoho informací také brožuru, kterou jsme dostali při vstupu, a fajn hrníček od O`Reilly & Associates.

Chceš čtenářům Roota něco sdělit?

eval pack "h*", "072796e6470222e456e2c5e622b3";

Hehe, to jsi řekl moc hezky. Díky za rozhovor.

Funkce

Nebudu již více rozebírat, na co funkce jsou a jak je používat. Přejdeme rovnou k vylomeninám s nimi. Předvedu jednu funkci a pak si ji rozebereme:

sub max {
    $a = shift;
    $b = shift;

    return $a if $a > $b;
    return $b
}

Ano, jedná se o funkci maximum, která vrátí maximum dvou čísel. Co vás překvapí jako první, bude asi absence formálních parametrů. V Perlu se parametry všech funkcí ukládají do fronty FIFO (First In First Out) se speciálním názvem @_. Funkce shift vrací první prvek daného pole, tohoto pole jej vyjme a celé posune, takže prvek na prvním místě se ocitne na nultém atd. Zvláštností funkce shift je to, že pokud jí programátor nezadá žádný parametr, pracuje se speciální hodnotou @_. Minule jsme se dozvěděli, že lze s výhodou využít seznamu (což také programátoři v Perlu dělají).

sub max {
    ($a, $b) = @_;

    return $a if $a > $b;
    $b
}

Přehlednější, že? Všechny přebývající parametry samozřejmě v poli @_ zbydou a můžeme si je dodatečně dovybírat. V příkladu jsem záměrně vypustil poslední volání funkce return. Každá funkce v Perlu vrací vyhodnocení posledního výrazu. Psát return před posledním výrazem je tedy zbytečné, jako by tam nebyl. Pozor na to! Pokud budete číst perlové programy jiných sofistikovanějších kolegů, ti to tam psát určitě nebudou.

Poznámku: pokud funkce shift nenalezne hodnotu @_ (a to tehdy, pokud nebyla volána z funkce), pracuje implicitně s polem @ARGV, ve kterém se nacházejí parametry, které byly předány programu. Toho můžete využít!

Každá funkce má tedy proměnný počet parametrů. Je na programátorovi, zdali při volání funkce max s třemi parametry zahlásí chybu typu příliš mnoho parametrů. Můžeme si implementovat funkci max tak, aby vrátila maximum z jakéhokoliv počtu parametrů.

Dozvěděli jsme se, že parametry jsou vlastně prvky pole, a víme, že pole se inicializuje seznamem. Podívejme se na volání takové funkce: „max (-100, 100);“ a zamysleme se. Vždyť je to vlastně volání funkce, jehož prvním parametrem je anonymní pole! Jak snadné, krásné a zárověň logické!

Funkce vyššího řádu

Díky odkazům a jisté speciální konstrukci lze vytvářet funkce vyššího řádu. Jsou to ty funkce, které akceptují jako jeden ze svých parametrů funkci nebo umějí vrátit funkci (kterou je možno si navázat na nějakjé jméno nebo ji rovnou použít – potom se jedná o užití tzv. anonymní funkce). Předávání funkcí se realizuje odkazem na funkci. Pokud máme v proměnné odkaz na funkci, můžeme ji zavolat dvěma ekvivalentními způsoby:

$funkce->();

&$funkce();

Nyní můžeme naše znalosti využít k tomu, abychom si vytvořili matematickou funkci suma, která se v matematice značí velkým řeckým písmenem sigma a sčítá výsledky hodnot funkcí v nějakém rozmezí. Například známá Leibnitzova posloupnost, která pro všechna přirozená čísla (zvyšovaná o 4) od 1 po x spočte hodnotu π / 8. Čím vyšší zvolíme číslo x, tím přesnější bude číslo π. Matematicky by to velmi zhruba bylo asi (nejsem matematik):

leibnitz
pro všechna přirozená čísla 1,5,9… (an-1 = a – 4)

V Perlu vytvoříme funkci, která bude přebírat čtyři parametry. Čísla a resp. b, která budou dolní resp. horní mezí, odkaz na funkci, kterou budeme sčítat, a odkaz na funkci, která vrátí další prvek posloupnosti:

# rekurzivní implementace
sub sum_rec {
    ($func, $a, $next, $b) = @_;
    return 0 if $a > $b;
    return &$func($a) + sum($func, &$next($a), $next, $b);
}

# to samé, ale iterativně
sub sum {
    ($func, $a, $next, $b) = @_;
    my $sum = 0;
    while ($a <= $b) {
        $sum += &$func($a);
        $a = &$next($a);
    }
    return $sum;
}

Pro obyčejný součet přirozených čísel 1 až 100 000 bychom volali funkci takto:

print "Suma: " . sum(sub {$_[0]}, 1, sub {$_[0]+1}, 100_000) . "\n";

Všiměte si dvou anonymních funkcí jako parametrů: první je funkcí identity (vrátí to samé) a druhá vrátí svůj první parametr zvýšený o 1, což je přesně to, co jsme potřebovali. Z příkladu je také patrné, jak se vlastně ty „tajemné“ anonymní funkce vytvářejí. Inu, jako všechno ostatní v Perlu, naprosto logicky. Je to stejné jako u pojmenovaných funkcí, s tím rozdílem, že nenapíšete název funkce. Tato konstrukce se zkompiluje a jejím výsledkem je odkaz na funkci. V našem případě jsme tento odkaz ihned použili jako parametr volané funkce, ale nic vám nebrání si jej uložit třeba do skaláru. Za povšimnutí stojí číslo „100_000“. Znak podtržítko v tomto případě odděluje řády a Perl si ho nevšímá.

Z výstupu 5000050000 je jasné, že při počítání není Perl nijak omezen. Zkuste si naprogramovat např. faktoriál a nechat si vrátit nějaké velké číslo. Uvidíte, že to Perl zvládne. Je to obrovská výhoda – můžete se soustředit na problém a ne na to, jaký že to typ proměnné bude vlastně nejvhodnejší.

Nyní přejděme zpět k Leibnitzovi a nechme Perl spočítat relativně nepřesné číslo π (jde to i jinými přesnějšími metodami):

print 8 * sum(
   sub {$n=shift; 1.0 / ($n*($n+2));},
   1,
   sub {$_[0]+4},
   100_000);

# vypíše "Pi: 3.14157265358981"

Jak jistě tušíte, pro Perl nebude problém interpretovat funkci, která jako svůj výsledek vrátí anonymní funkci. Anonymní funkce hrají hlavní roli při zobecňování postupů nebo při programování „okenních“ aplikací.

Uzávěry

S anonymními funkcemi jsou spojeny uzávěry (closures). Uzávěrem nazýváme anonymní funkci, která použila nějakou proměnnou definovanou v „nadřazeném“ prostředí. Od chvíle, kdy se provádění bloku uzavře, získá anonymní procedura přístup k této proměnné, i když by byla již nedostupná. Uzávěry se většinou nejlépe vytvářejí tak, že definujeme funkci s danými proměnnými a vrátíme anonymní funkcí, která tyto proměnné bude používat. Díky tomu, že sběrač smetí Perlu počítá právě i s uzávěry, se prostředí z funkce vytvářející uzávěr nesmaže. Uzávěr by se dal použít třeba takto:

sub vytvor_pocitadlo {
  my $p = 0;
  sub { $p++ };
}

$pocet_jablek = vytvor_pocitadlo();
$pocet_hrusek = vytvor_pocitadlo();

# vytvořil jsem dva uzávěry, každý má
# svou vlastní lokální proměnnou $p pro počet,
# protože při každém volání funkce vytvor_pocitadlo
# Perl vytvořil novou proměnnou $p -- dokážeme si to:

# nyní několikrát zvýšíme jablka
&pocet_jablek() for (0..4);

# a budeme zvyšovat a vypisovat (protože posledním
# výrazem v uzávěru bylo právě počítadlo)
for (0..5) {
  printf("Jablek je %d a hrusek %d\n", &$pocet_jablek, &$pocet_hrusek);
}

# Vypíše:
# Jablek je 5 a hrusek 0
# Jablek je 6 a hrusek 1
# Jablek je 7 a hrusek 2
# Jablek je 8 a hrusek 3
# Jablek je 9 a hrusek 4
# Jablek je 10 a hrusek 5

Na výše uvedeném ovocném příkladě je vidět, že se uzávěry v Perlu hned tak nepoužívají. V některých jazycích (Scheme, Lisp a spol.) však hrají podstatnou roli. Používají se například jako nosiče členských proměnných v OOP. Kupodivu však i v Perlu mají své opodstatnění. Pomocí uzávěrů se totiž provádějí různé triky v OOP, jako je například získání ukazatele na členskou metodu třídy. Já osobně jsem v praxi uzávěr ještě nikdy nepotřeboval. Rád bych si přečetl vaše názory na uzávěry a jejich použití v praxi v diskusi pod tímto článkem.

Další díl Perliček končí a já se budu muset opět na delší dobu odmlčet. Zkouškové období se blíží rychlým tempem a musím se věnovat také jednomu projektu, které připravuji s redakcí Roota jako překvapení. Takže nashledanou zase někdy u Perliček.

Použitá literatura:

[PLD] Kolektiv autorů: Perl documentation, distribuce Perlu
[PCB] T. Christiansen, N. Torkington: Perl Cookbook, O`Reilly, ISBN 1–56592–243–3
[AWP] J. Orwant, J. Hietaniemi, J. Macdonald: Mastering Algorithms with Perl, O`Reilly, ISBN 1–56592–398–7

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