Ruby po kapkách (4.) – podmínky a cykly

20. února 2009

V minulém díle jsme nakousli cyklus while, který je jednou ze základních řídích struktur procedurálních jazyků. Pojďme se nyní podívat, jaké další možnosti ovládání běhu programu nám Ruby nabízí.

Pro řízení cyklů a větvení se používá podmínek. Podmínkou může být jakýkoliv výraz. Důležité je, jaká je jeho pravdivostní hodnota. Jak jsme již uváděli, pravdivostní hodnoty jsou reprezentovány hodnotami true a false. Z pohledu „pravdivosti“ pro všechny ostatní hodnoty platí, že hodnota nil je totéž co false a jakákoliv jiná hodnota je totéž co true. Zapamatování tohoto pravidla je důležité zejména pro vývojáře zvyklé na jazyky inspirované jazykem C. Tam se například číslo 0 a prázdný řetězec vyhodnocují narozdíl od Ruby jako nepravda. (Je zajímavé, že na toto téma byly na internetu svedeny již mnohé vášnivé filosofické debaty. Cítíte-li potřebu se také vyjádřit, neváhejte a připojte svůj komentář.)

Velmi často se v podmínkách využívají porovnávací operátory. Ty nejběžnější vypadají prakticky ve všech programovacích jazycích stejně. Pojďme si je stručně představit v irb.

irb(main):001:0> 1 == 1
=> true
irb(main):002:0> 1 == 2
=> false
irb(main):003:0> 1 == ‚1‘
=> false
irb(main):004:0> 1 != 2
=> true
irb(main):005:0> 1 != 1
=> false
irb(main):006:0> 1 < 2
=> true
irb(main):007:0> 1 < 1
=> false
irb(main):008:0> 1 <= 1
=> true
irb(main):009:0> 2 > 1
=> true
irb(main):010:0> 2 >= 1
=> true
irb(main):011:0> ‚a‘ < ‚b‘
=> true

Opět platí, že tyto operátory jsou implementovány jako metody příslušných tříd a lze je předefinovat. Výjimkou je operátor nerovnosti !=, který interpret automaticky převádí na negaci rovnosti !(==). V případě definování operátoru == tedy získáme operátor != automaticky. (Na druhé straně nelze != definovat nezávisle.)

Pojďme se podívat, jak využít podmínek pro větvení pomocí výrazu s klíčovým slovem if. Obecná struktura výrazu je následovná (v hranatých závorkách jsou uvedeny nepovinná klíčová slova, jejichž využívání se spíše nedoporučuje):

if podmínka1 [then] větev1
elsif podmínka2 [then] větev2
else
větev3
end

Česky bychom to přeložili jako: jestliže je podmínka1 vyhodnocena jako pravda, vyhodnoť větev1, jinak pokud je podmínka2 vyhodnocena jako pravda, vyhodnoť větev2, jinak vyhodnoť větev3. Části uvozené elsif a else jsou nepovinné. Částí uvozených elsif může být libovolně mnoho. Oddělovačem těchto sekcí je buď konec řádku nebo středník.

Celá konstrukce if ... je sama o sobě výrazem a hodnotou tohoto výrazu je hodnota větve, která se nakonec vyhodnotí. Pojďme si to vyzkoušet v irb a zároveň si při tom převod jiných hodnot na true nebo false. Pro úsporu místa využijeme jednořádkovou variantu se středníkem, ačkoliv součástí dobrého stylu zápisu větvení v Ruby je víceřádková forma uvedená výše. (Pro úplnost uveďme, že případné then středník nahrazuje.)

irb(main):001:0> if 1 == 1; true else false end
=> true
irb(main):002:0> if nil; true else false end
=> false
irb(main):003:0> if 0; true else false end
=> true
irb(main):004:0> if “; true else false end
(irb):4: warning: string literal in condition
=> true

Jak se ukazuje, v případě použití řetězcového literálu, vypíše interpret dokonce varování. Nepodařilo se mi vypátrat skutečný důvod, proč vývojáři Ruby toto varování implementovali. Je možné, že je to ochrana před chybným uzavřením názvu proměnné do uvozovek či apostrofů. Může to ale také naznačovat, že využívání hodnot, které nejsou explicitně booleovské, není v definici podmínek zrovna doporučená technika.

Povšimněte si ještě nepoužívání závorek. Opět na rozdíl od jazykem C inspirovaných jazyků Ruby nevyžaduje závorky kolem podmínek. Podmínka se závorkami je sice platná, ale šetřme prsty a klávesnice.

Obráceně než výraz ifend se vyhodnocuje výraz unlessend.

unless podmínka [then] větev1
else
větev2
end

Česky: jestliže je podmínka vyhodnocena jako nepravda, vyhodnoť větev1, jinak vyhodnoť větev2. Část uvozená else je nepovinná. Konstrukce s unless je samozřejmě ekvivalentní konstrukci s if a negovanou podmínkou. Dá se říct, že unless forma je i o něco hůře čitelná. Mimochodem, jak jste již mohli vytušit, operátor pro negaci je ! případně klíčové slovo not.

irb(main):001:0> unless 1 == 1; ‚a‘ else ‚b‘ end
=> „b“
irb(main):002:0> if !(1 == 1); ‚a‘ else ‚b‘ end
=> „b“
irb(main):003:0> if not 1 == 1; ‚a‘ else ‚b‘ end
=> „b“

Zvídavého čtenáře možná zarazí uzávorkování druhé varianty, která je kromě jiné formy operátoru totožná s varinatou tři. Operátory ! a not mají sice stejnou funkci, ale liší se prioritou. Priorita operátoru ! je vyšší než priorita operátoru == a proto by interpret vztáhnul negaci hned k první jedničce a ne k výsledku porovnání obou jedniček. Naopak priorita operátoru not je velmi nízká.

Další zajímavou variantou je použití if a unless jako modifikátorů jiných výrazů. Zní to tajemně, ale v kódu to vypadá velmi přehledně a čte se to téměř anglicky.

výraz1 if podmínka1
výraz2 unless podmínka2

Jinými slovy výraz1 se zpracuje, když je podmínka1 vyhodnocena jako pravda. Pro výraz2 platí opak. Nakonec uveďme ještě jedno dědictví po jazyku C – takzvaný ternární operátor. Jedná se o zhuštěný zápis if podmínky.

podmínka ? výraz1 : výraz2

Když je podmínka vyhodnocena jako pravda je celková hodnota výrazu rovna hodnotě výrazu 1. V opačném případě je rovna hodnotě výrazu 2. V mnoha programovacích jazycích je if podmínka realizována jako příkaz zajišťující větvení, který ovšem nevrací žádnou hodnotu. Ternární operátor v nich pak tuto možnost nabízí. V Ruby umožňuje jen úspornější (a nejspíš hůře čitelný zápis).

irb(main):001:0> a = 1 < 2 ? ‚ano‘ : ‚ne‘
=> „ano“
irb(main):002:0> a = if 1 < 2; ‚ano‘ else ‚ne‘ end
=> „ano“

Teď přeskočme poslední klasický způsob větvení pomocí konstrukce case, které se budeme věnovat někdy později, a podívejme se na cykly. Již jsme viděli cyklus while s podmínkou na začátku.

while podmínka [do] tělo cyklu
end

Analogicky jako k if dělá protějšek unless, máme k while k dispozici zřídka užívaný protipól until. Jak while tak until lze také použít jako modifikátory.

until podmínka [do] tělo cyklu
end

Modifikátorová forma může svádět k dojmu, že se jedná o cyklus s podmínkou na konci. Ve skutečnosti se však jedná jen o syntaktickou vychytávku, kterou si interpret převede na kanonickou formu. Následující program tedy vypíše 0, protože přičítání do proměnné ve druhém řádku se vůbec nevyhodnotí.

a = 0
a = a + 1 while a < 0
puts a

Klasický céčkový for cyklus v Ruby nenajdete. To ovšem není chyba, nýbrž vlastnost – respektive návrhové rozhodnutí. Máme však k dispozici jiné a v mnohém elegantnější konstrukce, které si předvedeme na příkladech.

irb(main):001:0> 3.times { puts ‚ahoj!‘ }
ahoj!
ahoj!
ahoj!
=> 3
irb(main):002:0> 3.times { |i| puts i }
0
1
2
=> 3
irb(main):003:0> 1.upto(3) { |i| puts i }
1
2
3
=> 1
irb(main):004:0> 3.downto(1) { |i| puts i }
3
2
1
=> 3
irb(main):005:0> 0.step(9, 3) { |i| puts i }
0
3
6
9
=> 0

To, co vidíte, není nějaká zvláštní konstrukce jazyka, ale úplně normální volání metod. Číslo 3 má metodu times. (Pro přesnost: tato metoda je definována ve třídě Integer, která je předkem tříd Fixnum a Bignum.) Této metodě nepředáváme žádné parametry, ale kousek kódu uzavřený mezi složenými závorkami. Označujeme ho jako blok. Metoda pak spouští tento blok opakovaně tolikrát, kolik je číslo, na které, je motoda volána. V možnosti předávat kus kódu jako parametr poprvé vidíme inspiraci Ruby také v paradigmatu funkcionálního programování. S tímto nesmírně silným mechanismem se však setkáme ještě mnohokrát. Zatím jen doplňme, že místo složených závorek lze také použít dvojici klíčových slov do a end. Konvence praví, že pro jednořádkové bloky jsou lepší složené závorky a naopak.

Ve druhém pokus s metodou times využíváme toho, že metoda může bloku při volání předat parametr. V tomto případě to bude postupně inkrementovaný čítač, který si v bloku načteme do proměnné i. Další metody ukazují jiné obvyklé formy cyklů s vcelku intuitivními anglickými názvy.

Existuje také metoda loop, kterou můžeme volat bez příjemce. Sama o sobě by volala předaný blok donekonečna. Abychom ji mohli efektivně použít, musíme vědět ještě o dalších způsobech ovládání cyklů, z nichž si zatím uveďme příkazy break a next. break ukončuje provádění cyklu (případně opakované volání bloku). next přeskakuje na konec aktuální iterace (případně aktuálního volání bloku). Jinými slovy vyvolává okamžitý přechod na následující iteraci. Využití demonstruje následující krátký program, který vypíše všechna čísla od jedné do deseti kromě pětky.

a = 0
loop do
a = a + 1
next if a == 5
puts a
break if a == 10
end

Ruby je bohatý jazyk, který se příliš nepřiklání k filozofii, že na všechno by měla být právě jedna nejsprávnější cesta, jak to udělat. Berte proto předchozí letmý úvod do problematiky větvení a cyklů jako … letmý úvod. V dalších pokračování budeme jistě postupně odkrývat různé další zajímavosti a možnosti řízení Ruby programů. Na druhou stranu nejjednoduší programovací techniky jsou v praxi ty nejcenější, proto nemá cenu používat nejnovější experimentální vlastnost tam, kde postačí while.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *