Ruby z rychlíku

Autor: Dalibor Šrámek, email: dalibor.sramek@insula.cz. Verze 0.5. Poslední úprava 4. února 2002.

Ruby
Ruby - SLT02
Ruby z rychlíku
Ruby a OOP
Ruby Class Hierarchy
DbTalk

Obsah

Úvodem

Ruby je mladý, (zatím) interpretovaný, objektově orientovaný programovací jazyk, vytvořený a nezávisle vyvíjený Yukihirem Matsumotem. Interpret jazyka je dobře přenositelný a momentálně existuje pro různé varianty UNIXu, MS Windows, Mac OS, OS/2 a BeOS. Nejstručnější charakteristika praví, že Ruby=(Perl+Smalltalk)/2.

Základním zdrojem informací o Ruby je www.ruby-lang.org a vynikající on-line kniha Programming Ruby.

Kdy vás může Ruby zaujmout? Zejména pokud splňujete některou z následujících podmínek:

Článek předpokládá určitou zkušenost a orientaci v běžných pojmech procedurálního a objektového programování.

Hello, World!

Přejděme tedy k praxi. Po instalaci můžete (za obvyklých podmínek) ihned spustit interpret v interaktivním režimu pomocí příkazu irb. Irb ihned vyhodnocuje každý zadaný řádek:
irb 0.6.1(99/09/16)
irb(main):001:0> 1+1
2
irb(main):002:0>
Dialog s irb ukončíte stiskem Ctrl+D.

Kód programu vytvořený libovolným editorem lze samozřejmě také uložit do souboru (obyčejně s příponou rb) a spustit příkazem ruby <soubor>.

Obligátní HelloWorld.rb by mohl vypadat například takto:

#!/usr/local/bin/ruby

# Nadefinujeme funkci bez parametrů. Definice končí příkazem end.
def helloWorld_1
  # Funkce puts vypisuje parametry na standardní výstup.
  puts 'Hello, world!'
end

# Zavoláme nadefinovanou funkci.
helloWorld_1
Asi nás nepřekvapí výsledek:
Hello, world!
Podívejme se, co lze zjistit pohledem na kód tohoto jednoduchého příkladu: Udělejme malinko složitější HelloWordl2.rb (možná ho plně pochopíte až po dočtení článku):
#!/usr/local/bin/ruby

# Podle toho, zda objekt zadaný parametrem reaguje
# na metodu 'times' funkce zobrazí n krát text nebo
# přímo zadaný parametr.
def helloWorld_2(n)
  if n.respond_to?(:times)
    n.times { |x| puts "#{x}, Hello, world!" }
  else
    puts n
  end
end

helloWorld_2(3)
helloWorld_2('Konec')
Po spuštění získáme:
0, Hello, world!
1, Hello, world!
2, Hello, world!
Konec
Vidíme, že:
a=33
puts '1: abc \n def #{a}'
puts "2: abc \n def #{a}"
puts `date`
Vypíše:
1: abc \n def #{a}
2: abc 
 def 33
Fri Feb  1 09:58:58 CET 2002

Proměnné a operátory

Základní fakta:

Čísla

Ruby podporuje celá i reálná čísla. Celá čísla odpovídající 32bitovému nebo 64bitovému integeru (podle platformy) jsou interně uložena jako obejkt třídy Fixnum. Větší celá čísla jsou ukládána jako objekt třídy Bignum. Bignum umožňuje pracovat s integery, jejichž velikost je omezena jen velikostí paměti počítače. Protože typy všech proměnných jsou v Ruby určovány až za běhu programu je i přechod mezi dvěma integerovými typy bezbolestný:
n=8
7.times do                # blok pro opakování je uzavřen mezi 'do' a 'end'
  puts "#{n.type}  #{n}"  # metoda 'type' vrací typ proměnné
  n*=n
end
Vypíše:
Fixnum  8
Fixnum  64
Fixnum  4096
Fixnum  16777216
Bignum  281474976710656
Bignum  79228162514264337593543950336
Bignum  6277101735386680763835789423207666416102355444464034512896
Celá čísla je také možné zapisovat jako binární, oktalová nebo hexadecimální. Dekadické číslo 513 lze zapsat také jako: Protože jsou čísla objekty, ukažme si několik základních metod, které mají definovány:
puts -23.abs          # absolutní hodnota
puts 65.chr           # znak, jehož ASCII kód je roven číslu
puts 3.next           # o jedničku vyšší číslo
puts 0.zero?          # vrací 'true', když je číslo rovno nule
puts 10.remainder(3)  # zbytek po dělení čísla parametrem
puts 15.type          # typ proměnné
Jak jistě tušíte, výsledkem je:
23
A
4
true
1
Fixnum

Stojí za zmínku, že většina operátorů, o kterých bude řeč později, jsou ve skutečnosti metody s upravenou syntaxí volání: a+b můžeme napsat jako a+(b), což je volání metody '+' objektu 'a' s parametrem 'b'.

Reálná čísla jsou uchovávána jako objekty třídy Float a to ve dvojnásobné přesnosti. Float poskytuje několik základních metod:

puts 3.14.round   # zaokrouhlení podle matematických pravidel
puts 3.14.ceil    # nejbližší větší celé číslo
puts 3.14.floor   # nejbližší menší celé číslo
puts -3.14.round
puts -3.14.ceil
puts -3.14.floor
Nepřekvapí nás výsledek:
3
4
3
-3
-3
-4
Zbytek aritmetických operací je soustředěn v tzv. modulu Math a jejich vysvětlení přesahuje rozsah tohoto článku.

Řetězce

Řetězcové proměnné jsou v Ruby uchovávány v objektech třídy String. Jsou to v podstatě sekvence 8bitových znaků, takže v objektu String lze uchovávat i binární data (například načtená ze souboru). Obvykle se řetězce inicializují pomocí konstant uzavřených v uvozovkách nebo apostrofech a platí stejná pravidla pro vyhodnocování obsahu, jaká byla uvedena výše. Navíc nabízí Ruby pohodlnou cestu k inicializaci dlouhých, několikařádkových řetězců:
s1='Neinterpretuje\nse'
s2="Interpretuje\nse 1+1 = #{1+1}"
s3=<<KONEC
  Tento řetězec může mít několik řádků a končí
  až slovem, které je uvedeno za znaky '<<'.
  (Počítá se do něj i případné odsazení řádků.)
KONEC

puts s1
puts s2
puts s3
Výsledkem je:
Neinterpretuje\nse
Interpretuje
se 1+1 = 2
  Tento řetězec může mít několik řádků a končí
  až slovem, které je uvedeno za znaky '<<'.
  (Počítá se do něj i případné odsazení řádků.)
Protože práce s řetězci je programátorovým denním chlebem, poskytuje String opravdu velké množství metod pro jejich manipulaci. Uveďme si jen několik jednoduchých příkladů:
puts 'HellO'.downcase         # převod do malých písmen
puts 'hello'.upcase           # převod do velkých písmen
puts 'hello'.length           # délka řetězce
puts 'hello'.include?('el')   # vrací 'true', pokud řetězec obsahuje parametr
puts 'hello'.index('el')      # pozice parametru v řetězci
puts 'hello'.reverse          # obrácený řetězec
puts 'hello'.reverse.reverse  # návratová hodnota je zase objekt, jehož
                              # metodu můžeme volat
puts '   hello   '.strip      # odstranění prázdných znaků ze začátku a konce
puts 'hello'.tr('he','Ha')    # zaměňuje znaky (jako příkaz 'tr' v UNIXu)
Získáme:
hello
HELLO
5
true
1
olleh
hello
hello
Hallo
Navíc můžeme počítat se silnými nástroji pro nahrazování a změny částí řetězců a pro práci s regulárním výrazy, o nichž bude ještě řeč.

Možná jste si již všimli konvence, kdy názvy metod vracejících hodnotu boolean končí otazníkem. Podobně exitují metody, jejichž název končí vykřičníkem. Vykřičník upozorňuje na to, že dochází přímo ke změně samotného objektu. (Normální metody vrací hodnotu, ale stav objektu zůstává nezměněn).

a='hello'
puts a
puts a.reverse   # jen vrať hodnotu
puts a
puts a.reverse!  # vrať hodnotu a změň objekt
puts a
Vrací:
hello
olleh
hello
olleh
olleh

True, false, nil

Nebudeme-li se snažit o zbytečnou složitost, můžeme říct, že v Ruby jsou definovány tři speciální hodnoty, kterých také může nabýt proměnná: Použití osvětlí příklad:
puts 2<3               # to je pravda
puts 3<2               # to není pravda
puts 'abcd'.index 'r'  # znak v řetězci není - vrací se doslova "nic"
S výsledkem:
true
false
nil

Pole

K uložení jednorozměrného pole slouží objekt třídy Array. Prvkem pole může být libovolný další objekt včetně pole samotného, čímž lze docílit vícerozměrných polí. Array poskytuje automatickou alokaci paměti a kontrolu mezí indexů. Podobně jako u řetězců i pro pole existuje značné množství metod. Nejlépe si vše osvětlíme na příkladech:
def puts_ary ary      # uděláme si funkci na výpis pole
  puts ary.join ', '  # vypiš prvky spojené čárkami a mezerami do řetězce
end

a=['p','o','l','e']   # inicializujeme pole čtyř jednoznakových řetězců

puts a.length         # počet prvků pole (také můžeme použít a.size)
puts a[1]             # prvek s indexem 1 (indexy začínají od nuly)
puts a[-1]            # první prvek od konce pole
puts a.index 'o'      # index prvku, který se rovná 'o'

puts_ary a[2..3]      # pole tvořené prvky s rozsahem indexů 2..3

a[4]=1                # přiřadíme další prvek, pole se automaticky rozšíří
puts_ary a
a.delete_at 4         # zase prvek smažeme (prvek s indexem 4)
puts_ary a

puts_ary a.sort       # získáme seřazené prvky pole
puts_ary a.reverse    # prvky pole v obráceném pořadí

a.push 'x'            # přidání prvku na konec pole
puts_ary a
puts a.pop            # odebrání prvku z konce pole
puts_ary a
a.unshift 'x'         # přidání prvku na začátek pole
puts_ary a
puts a.shift          # odebrání prvku ze začátku pole
puts_ary a
Zobrazí se:
4
o
e
1
l, e
p, o, l, e, 1
p, o, l, e
e, l, o, p
e, l, o, p
p, o, l, e, x
x
p, o, l, e
x, p, o, l, e
x
p, o, l, e
Pomocí metod push, pop, shift a unshift lze realizovat klasické struktury zásobníku a fronty. Tyto metody mění dané pole. U jiných platí stejné pravidlo s vykřičníkem jako u řetězců (pro metodu sort existuje ekvivalent sort! atd.)

Hash

Hash je struktura podobná poli (někdy se mu také říká asociativní pole), ale na rozdíl od pole, kde je jako index pevně stanoven integer, může být indexem hashe prakticky libovolný objekt. K ilustraci opět využijeme jednoduchý příklad:
a={'jedna'=>'I',     # hash můžeme inicializovat pomocí čárkami
   'dve'=>'II',      # oddělených přiřazení klíč=>hodnota
   'tri'=>'III',
   'ctyri'=>'IV',    # v tomto případě použijeme pro klíč i hodnoty
   'pet'=>'V'        # řetězcové konstanty
}

puts a['dve']        # hodnota s klíčem 'dvě'
puts a['deset']      # hodnota s klíčem 'deset' (není tam, vrací se nil)
a['deset']='X'       # přiřadíme klíči 'deset' hodnotu 'X'
puts a['deset']      # hodnota s klíčem 'deset'
puts a.index 'V'     # klíč hodnoty 'V'
puts a.invert['IV']  # metoda invert vytvoří obrácený hash (hodnota=>klíč)
puts a.length        # počet záznamů v hashi
Po spuštění získáme:
II
nil
X
pet
ctyri
6
Je možné ukládat do hashů pole a naopak, což nabízí značnou flexibilitu pro konstrukci různých datových struktur. Další možnosti práce s poli a hashi uvidíme v sekci o iterátorech.

Operátory

Samozřejmostí jsou operátory pro běžné aritmetické operace a obvyklé relační operátory. Podívejme se na několik dalších, které by nás mohly zajímat: O použití se snadno přesvědčíme na příkladu:
a,b,c=1,2,3           # do a,b a c postupně přiřadíme 1,2 a 3
puts "#{a+b+c}"       # součet a,b a c 
puts 2**5             # dvě na pátou
puts 3<=>5            # porovnání 3 a 5
puts 3>4?1:2          # pokud platí 3>4, zobraz 1, jinak 2
puts 4.between?(3,5)  # je 4 mezi 3 a 5?
puts 'a'+'b'          # spojení řetězců
puts 'a'*3            # opakování řetězce
Získáme:
6
32
-1
2
true
ab
aaa

Regulární výrazy

Regulární výrazy jsou mocnou zbraní skriptovacích jazyků a do Ruby jsou elegantně integrovány. Pro naše účely postačí velmi zjednodušená a stručná pravidla: Více osvětlí několik jednoduchých příkladů:
puts 'ahoj'=~/ho/
puts 'ahoj'=~/kk/
puts 'ahoj'!~/ho/
puts 'ahoj'!~/kk/

# složitější regulární výraz pro jednoduchou kontrolu, zda řetězec má formát
# datumu d.m.rrrr
puts '2.2.2002'=~/^[0-9][0-9]?\.[0-9][0-9]?\.[0-9]{4}$/
puts '245.2.2002'=~/^[0-9][0-9]?\.[0-9][0-9]?\.[0-9]{4}$/
Po spuštění uvidíme:
1
nil
false
true
0
nil
Regulární výrazy se používají i jako parametry pro některé metody. Na ukázku si doplníme znalosti metod třídy String:
s='Perl Pike Python Perl Pike Python Perl Pike Python'
puts s
s.sub!(/Perl/,'Ruby')     # nahraď první výskyt slova 'Perl' za slovo 'Ruby'
s.gsub!(/Python/,'Ruby')  # nahraď každý výskyt slova 'Python' za slovo 'Ruby'
puts s
Vypíše:
Perl Pike Python Perl Pike Python Perl Pike Python
Ruby Pike Ruby Perl Pike Ruby Perl Pike Ruby
Použití regulárních výrazů je daleko více. Navíc jsme se zabývali jen konstantními výrazy, ale regulární výraz je v Ruby objekt jako každý jiný, a je možné ho bez problémů vytvářet za běhu programu.

Řídící struktury

Podmínky

Bez podmíněných příkazů a cyklů bychom se v programování daleko nedostali. Už jsem se setkali s podmínkou if. Tělo podmínky je zpracováno pouze tehdy, je-li výraz vyhodnocen jako true.
a=10
if a>15 then puts 'a>15'     # tělo podmínky je oddělené slovem 'then'
elsif a>10 then puts 'a>10'
elsif a>5                    # tělo podmínky je na novém řádku
  puts 'a>5'
else
  puts 'a<=5'
end
Výsledkem je pochopitelně:
a>5
Použití then není povinné, pokud tělo podmínky začíná až na dalším řádku. Obrácenou podmínku lze realizovat pomocí příkazu unless.

Cykly

Základním cyklem při programování je while. Tělo cyklu se provádí tak dlouho, dokud je výraz vyhodnocován jako true.
a=0
b=[]              # prázdné pole
while a<5
  b.push a        # přidáme hodnotu z a na konec pole b
  a+=1
end
puts b.join ', '  # join vrací řetězec s hodnotami pole spojenými parametrem
Program vypíše:
0, 1, 2, 3, 4
Jak příkaz if, tak while lze využít ještě jedním elegantním způsobem:
a=0
b=[]	
puts 'O.K.' if a==0
b.push a+=1 while a<5
puts b.join ', '
Výsledkem je:
O.K.
1, 2, 3, 4, 5
Zatímco podmínka if se chová podle očekávání, cyklus poskytuje jiné výsledky než v předchozím případě. Je to způsobeno tím, že již před uložením do pole je proměnná a inkrementována.

Iterátory

Než si ukážeme, co je to iterátor, řekneme si, jak lze v Ruby vytvořit blok kódu. Blok není nic jiného než určitým způsobem ohraničený kousek kódu. Ohraničení lze provést buď složenými závorkami nebo klíčovými slovy do a end. Sám o sobě nemá blok velký smysl, lze ho však předat jako parametr metodě, která ho může opakovaně volat. Taková metoda se nazývá iterátor.
3.times { print '*' }         # s metodou 'times' jsme se již setkali -
puts                          # provede blok tolikrát, kolik je hodnota čísla
3.times { |n| print n }       # a hodnotu iterace může do bloku předávat jako
puts                          # parametr
3.upto(5) { |n| print n }     # spolu s metodami 'upto' a 'step' prakticky
puts                          # nahrazuje for cyklus, který v ruby není
3.step(10,2) { |n| print n }
puts

a=['a','b','c']               # malé pole znaků

a.each { print '*' }          # pole má také iterátory - each iteruje přes
puts                                 # všechny prvky
a.each { |n| puts "#{n}, Hello!"  }  # do bloku předává aktuální prvek

b=a.select { |x| x>='b'}  # v tomto případě vrací metoda 'select' jen prvky,
puts b.join ', '          # pro které volaný blok vrací true
Výsledkem bude:
***
012
345
3579
***
a, Hello!
b, Hello!
c, Hello!
b, c
Iterátorů je v Ruby mnohem více a snadno si lze také vytvořit vlastní. Největší využití najdou zřejmě při nahrazování běžných cyklů, a zejména pro práci s prvky kontejnerů - tj. pole, hashe a dalších.

Komunikace se světem

Snad žádný proram se neobejde bez komunikace se svým okolím. V Ruby není těžké pracovat jak se standardním vstupem a výstupem, tak se soubory nebo síťovými sockety.

Standardní výstup

Uvedené ukázky zatím používaly pouze funkci puts, která vypíše parametr na standardní výstup a přidá znak konce řádku. Podobnou funkcí je print, který však znak konce řádku nepřidává. K dispozici je klasický printf, jehož formátovací specifikace je podobná té v jazyce C.
puts 'a'    # vypiš 'a' a odřádkuj
print 'b '  # vypiš 'b' a neodřádkuj
puts 'c'
printf "Číslo: %5.2f, řetězec: %s\n", 3.14, 'PI'    # \n bude konec řádku
Po spuštění uvidíme:
a
b c
Číslo:  3.14, řetězec: PI
Zjednodušeně řečeno se při volání těchto funkcí ve skutečnosti volá metoda stejného jména náležející objektu třídy IO. Konkrétně se v našem případě jednalo o objekt reprezentující standardní výstup programu, který je přístupný přes globální proměnnou $stdout.
$stdout.puts 'Tak tak'  # výpis na stdout
(Poznámka pro pozorné čtenáře: znak '$' není na začátku názvu proměnné náhodou. V Ruby existuje konvence pro pojmenování proměnných, která říká, že jména globálních proměnných začínají právě znakem '$'. Ostatní proměnné, které používáme v příkladech jsou lokální a jejich jména začínají na malé písmeno.)

Standardní vstup

Ze standardního vstupu můžeme načíst řádek pomocí funkce gets. Opět se volá metoda objektu třídy IO - tentokrát $stdin. Pro čtení máme k dispozici i další metody. Podívejme se na příklady. Následující cyklus načítá řádky ze standardního vstupu a kopíruje je na standardní výstup:
while line=gets  # buď načte řádek a pokračuje nebo vrací nil a cyklus končí
  print line     # vypiš řádek na stdout (gets ponechala znak konce řádku)
end
Pokud bychom chtěli nejdříve načíst všechny řádky a pak je zpracovávat, mohli bychom to udělat například takto:
a=$stdin.readlines  # načti řádky do pole řetězců
puts a.size         # počet prvků pole - tj. počet načtených řádků

Soubory

Viděli jsme, že vstup a výstup je realizován pomocí metod třídy IO. Kromě standardního vstupu a výstupu můžeme využít také soubor nebo třeba síťový socket. Jak přečíst soubor?
f=File.new("soubor", "r")  # metoda new vytvoří objekt třídy File
while line=f.gets          # úplně stejně jako u standardního vstupu
  print line
end
f.close                    # zavřeme soubor
Pro účely přístupu k souboru se využívá třídy File, která je velmi podobná třídě IO - můžeme volat všechny metody pro vstup a výstup, které jsme dosud použili. Metoda new, která vytváří nový objekt a v našem případě otevírá soubor má dva parametry. První určuje název souboru a druhý mód otevření souboru (např. 'r' znamená read - čtení, 'w' znamená write - zápis).

Ukažme si ještě obecnost některých principů v Ruby:

f=File.new("soubor", "r")   # metoda new vytvoří objekt třídy File
f.each_line { |line|        # iterátor volá blok a předává mu řádky
  print line
}
f.close                     # zavřeme soubor
Se souborem můžeme pohodlně pracovat pomocí iterátorů. V našem příkladu jsme zpracovávali soubor opět po řádcích, ale máme k dispozici i iterátor načítající soubor po bytech.

Argumenty a proměnné prostředí

Vstupními parametry programu mohou být samozřejmě také argumenty zadané z příkazové řádky a proměnné prostředí, ve kterém je program spuštěn. Argumenty jsou uloženy v globální proměnné ARGV, ke které se přistupuje stejnými metodami, jaké má třída Array. Oproti jazyku C není ARGV[0] cesta ke spuštěnému programu, ale skutečně první zadaný argument. Cesta ke spuštěnému programu se nalézá v globální proměnné $0.

Proměnné prostředí jsou uloženy v proměnné ENV, ke které lze přistupovat stejnými metodami jako má třída Hash. Nejlépe je vše vidět na příkladu:

puts "Program: #{$0}"
puts "Argumenty z příkazové řádky: #{ARGV.join(', ')}"
puts "Proměnné prostředí:"
ENV.each { |key,value|
  puts "#{key} = #{value}"
}
Pokud uvedený kód bude uložen v souboru program.rb a spustíme jej pomocí příkazu ruby program.rb par1 par2 par3, obdržíme výpis podobný tomuto (výpis proměnných je krácen):
Program: test.rb
Argumenty z příkazové řádky: par1, par2, par3
Proměnné prostředí:
PWD = /home/dali/aoi/prg/ruby/tmp
PAGER = less
USER = dali
COLORTERM = Eterm
SHELL = /usr/local/bin/bash
HOSTTYPE = i386
OSTYPE = freebsd4.4

Ošetření výjimek

Jako moderní programovací jazyk nabízí Ruby solidní podporu ošetření výjimek. K pochopení jejího fungování nejlépe poslouží demonstrativní prográmek.
#!/usr/local/bin/ruby

begin                               # začátek bloku s výjimkami
  while line=gets                   # čti každý řádek až do konce souboru
    raise "Aaaa!" if line=~"^[aA]"  # způsob výjimku, když řádek začíná na 'a'
    puts line                       # vypiš řádek
  end                               # konec cyklu (zde končí běžný program)
rescue RuntimeError                 # odchyť výjimku třídy 'RuntimeError'
  puts "Chyba A!"                   # zobraz hlášku
rescue Interrupt                    # odchyť výjimku třídy 'Interrupt'
  puts "\nKonec = CTRL-D."          # zobraz hlášku
  retry                             # zpět na začátek bloku
else                                # jinak (tj. pokud nedošlo k výjimce)
  puts "\nNedošlo k výjimce."       # zobraz hlášku
ensure                              # tento kód se vykoná tak jako tak -
  puts "\nKonec!"                   # ať došlo k výjice či nikoliv
end                                 # konec bloku s výjimkami
Tento program může skončit bud korektně na konci souboru, který na standardním vstupu vytvoříme stiskem CTRL-D, nebo vznikem výjimky. Jednu výjimku způsobujeme v kódu přímo příkazem raise (jedná se o výjimku třídy RuntimeError. Druhá výjimka třídy Interrupt vznikne stiskem CTRL-C (tedy požadavkem na přerušení provádění programu).

Zatímco RuntimeError pouze zachytíme, zobrazíme hlášku a program se ukončí, po zachycení Interrupt se pomocí příkazu retry vrátí řízení na začátek bloku označený klíčovým slovem begin. Za příkazem else následuje kód, který se vykoná, pokud k žádné výjimce nedošlo. Za příkazem ensure následuje kód, ketrý se vykoná v každém případě, ať již v bloku k výjimce došlo či ne. Příkazy retry, else a ensure samozřejmě nejsou k fungování mechanismu výjimek nutné. Eventuální nezachycená výjimka ukončí činnost programu.

Podle konkrétních podmínek může spuštění proběhnout různě. Při korektním ukončení takto (je vidět i vstup od uživatele):

^D
Nedošlo k výjimce.

Konec!
Na tomto příkladu vidíme zotavení po stisku CTRL-C a pak ukončení na výjimce vyvolané řádkem začínajícím na 'a':
ppp
ppp
^C
Konec = CTRL-D.
aaa
Chyba A!

Konec!
V obou případech dojde k vypsání zprávy 'Konec!'.

Ošetření chyb a nestandardních situací v programech je zřejmě nejpracnější částí programování. Ačkoliv se jimi již nebudeme zabývat, nabízí Ruby ještě další cesty pro jejich zvládnutí.

Standardní knihovna

Veškeré příklady v tomto článku jsou založeny pouze na vestavěných objektových třídách a metodách jazyka Ruby. Pokud se dostaneme do situace, kdy si s těmito prostředky nevystačíme, čeká nás ještě velmi bohatá standardní knihovna. Jen namátkově naznačme, co lze od standardní knihovny Ruby očekávat: Za všechny uvedené oblasti si ukažme alespoň jeden příklad. Jedná se o jednoduchý TCP server, který přijme (libovolný) jednořádkový požadavek na portu 8081 a vrací krátký HTML dokument s aktuálním časem. Po spuštění si lze činnost serveru ověřit nasměrováním prohlížeče na adresu http://localhost:8081.
#!/usr/local/bin/ruby

# Jednoduchý HTTP server naslouchající na portu 8081 vrací aktuální čas.

# načteme potřebný modul standardní knihovny
require 'socket'

# hlavička dokumentu zaslaná klientovi
head="HTTP/1.1 200/OK\r\nContent-type: text/html\r\n\r\n"

# text zaslaný klientovi - znaky xxx později nahradíme aktuálním časem
text=<<"EOS"
<html>
<body>
<h1>Právě je xxx</h1>
</body>
</html>\r\n
EOS

# vytvoříme instanci TCP serveru
server=TCPServer.new('localhost',8081)

begin                                  # budeme zachytávat výjimku
  puts "Start serveru..."
  while (session=server.accept)        # čekáme na spojení
    puts "Požadavek: #{session.gets}"  # přečteme data a vypíšeme je na stdout
    session.print head                 # vypíšeme hlavičku
    send=text.sub(/xxx/,Time.now.to_s) # nahradíme xxx za aktuální čas
    session.print send                 # vypíšeme připravený text
    session.close                      # uzavřeme spojení
  end
rescue Interrupt                       # stisk CTRL-C korektně ukončí server
  puts "\nStop serveru..."
end
Výpisy skriptu mohou vypadat třeba takto:
Start serveru...
Požadavek: GET / HTTP/1.0
Požadavek: GET / HTTP/1.0
^C
Stop serveru...
Kromě standardní knihovny se stále rozšiřuje množství knihoven vytvořených programátory pro všechny představitelné úlohy. Jejich patrně nejúplnější seznam je k dispozici na stránce The Ruby Application Archive.

Příklad programu

Abychom se nezabývali stále jen za vlasy přitaženými příklady, podívejte se na výpis krátkého, ale relativně smysluplného programu, který faktorizuje čísla zadaná jako parametry s příkazové řádky nebo načítaná ze standardního vstupu.
#!/usr/local/bin/ruby
#
# factor.rb - triviální faktorizace čísla, příklad ke článku Ruby z rychlíku
#

# Funkce factor vrací první nalezený dělitel čísla n, případně číslo n
# samotné, pokud je to prvočíslo.
def factor n
  f=2                              # dělitele začneme hledat od 2
  while f<=math.sqrt(n)            # dělitel nebude větší než odmocnina čísla
    return f if n.remainder(f)==0  # pokud je zbytek 0, je f dělitel a končíme
    f+=1                           # jinak inkrementujeme f a opakujeme cyklus
  end
  return n                         # nebyl nalezen dělitel, vracíme n
end

# funkce factorize hledá dělitele čísla n a ukládá je do pole, které je
# návratovou hodnotou. pokud je číslo n prvočíslem, vrátí se v poli jen
# jeden prvek - samotné n.
def factorize n
  x,a=n,[]                # do x uložíme n a vytvoříme prázdné pole a
  while x!=(y=factor(x))  # dokud x není prvočíslo (x==factor x, viz fce factor)
    a.push(y)             # y je dělitelem x, uložíme y do pole faktorů
    x/=y                  # x vydělíme y a pokračujeme faktorizací výsledku
  end
  a.push(y)               # uložíme poslední výsledek fce factor
  return a                # vrací se pole faktorů
end

# funkce printfactors vypíše na standardní výstup číslo n a jeho faktory.
def printfactors(n,a)
  print n.to_s.ljust(10)+': '  # n převedeme na string a doplníme mezerami
  if a.size>1                  # pokud jsou v poli faktory,
    puts a.join(', ')          # vypíšeme je oddělené čárkami,
  else                         # jinak
    puts 'je prvočíslo'        # je n prvočíslo
  end
end
  
help=<<EOS
  factor.rb - triviální faktorizace čísla, příklad ke článku Ruby z rychlíku
  použití: factor.rb [číslo1 číslo2 ...]
  (pokud nejsou zadány parametry, načítá ze standardního vstupu)
EOS

# Zde začíná hlavní část programu.
if ARGV.size>0                           # jsou-li parametry z příkazové řádky,
  if ARGV[0]=~/-h|--help/                # zkontroluj, zda není žádost o help
   puts help                             # výpis helpu
   exit                                  # normální ukončení programu
  end
  ARGV.each { |n|                        # jinak projdi parametry po jednom,
    n=n.to_i.abs                         # převeď na integer a absolutní hodnotu
    printFactors(n,factorize(n)) if n>0  # a faktorizuj ty větší než nula
  }
else                                 # když nejsou argumenty
  begin                              # budeme zachytávat výjimky
    while n=gets                     # načítej řádky ze standardního vstupu,
      n=n.to_i.abs                   # převeď na integer a absolutní hodnotu
      raise if n==0                  # pokud to bylo např. písmeno, výjimka
      printFactors(n,factorize(n))   # jinak faktorizuj
    end
  rescue Interrupt                   # stisk CTRL-C
    puts "\n* Stiskněte CTRL-D!"     # potřebujeme " kvůli \n
    retry                            # opakujeme od začátku
  rescue RuntimeError                # 'ručně' vyvolaná výjimka
    puts '* Neplatný vstup!'
    retry                            # opakujeme od začátku
  end
end
Po spuštění bez parametrů může dialog s uživatelem vypadat například takto:
23456789
23456789  : je prvočíslo
123456789
123456789 : 3, 3, 3607, 3803
a
* Neplatný vstup!
^C
* Stiskněte CTRL-D!
as
* Neplatný vstup!

Na co místo nezbylo

Ruby má za sebou přeci jen několik let vývoje a jedná se o značně bohatý jazyk. Jak z hlediska svých vlastností, tak vytvořených knihoven a aplikací. Stručný průřez v tomto článku nemůže být ani zdaleka kompletní. Z nejzajímavějších témat, o nichž nebyla řeč, vyberme například:

Zdroje