Ruby po kapkách (4.) – podmínky a cykly
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 if
… end
se vyhodnocuje výraz unless
… end
.
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.
Mohlo by vás také zajímat
-
10 nejpopulárnějších programovacích jazyků a jejich využití
9. listopadu 2023 -
Členská sekce: 4 důvody proč ji mít na svém webu
12. března 2024 -
9 nejzajímavějších doménových koncovek
19. srpna 2024 -
Nejnovější
-
Znovuuvedení domény .AD
5. září 2024 -
Jak vybrat doménu: Co je dobré vědět?
2. září 2024 -
Proč je důležité tvořit obsah na váš web?
29. srpna 2024 -
Co je to VRAM a jak ji navýšit bez drahého upgradu?
20. srpna 2024