FIGYELEM! Ez a dokumentum kizárólag az ELTE IK hallgatók számára oktatási célra készült! Félkész munka, dolgozunk rajta! Terjeszteni, felhasználni máshol a szerzők engedélye nélkül tilos!

Kifejezések


Mindezidáig játszi könnyedséggel kezeltük a Ruby kifejezéseit. Végtére is a=b+c egy elég általános dolog. Egy egész rakás Ruby programot írhatunk e fejezet elolvasása nélkül.

De nem lenne olyan mókás. ;-)

Az első különbség a Ruby-ban, hogy minden, ami ésszerűen egy értéket adna vissza, az meg is teszi - szinte minden kifejezés. Mit jelent ez a gyakorlatban?

Néhány nyilvánvaló dolgot, mint például az utasítások láncba fűzését.

a = b = c = 0
»  0
[ 3, 1, 7, 0 ].sort.reverse
»  [7, 3, 1, 0]

Kevésbé egyértelmű, hogy minden, ami normálisan Java-ban, vagy C-ben utasítás, az a Ruby-ban kifejezés. Például, mind az if, mind a case utasítás az utolsó futtatott kifejezés értékét adják vissza.

songType = if song.mp3Type == MP3::Jazz
  if song.written < Date.new(1935, 1, 1)
    Song::TradJazz
  else
    Song::Jazz
  end
else
  Song::Other
end
rating = case votesCast
  when 0...10    then Rating::SkipThisOne
  when 10...50   then Rating::CouldDoBetter
  else                Rating::Rave
  end

Operátor kifejezések

A Ruby-ban megtalálhatók az alap operátorok (+,-,*,/, és így tovább), valamint tartogat néhány meglepetést. Az operátorok, és teljes precedenciájuk lásd később.

A Ruby-ban sok operátor valójában metódushívás. Amikor a*b+c-t írunk, tulajdonképp megkérjük az a objektumot, hogy futtassa le a * metódust a b paraméterrel. Ezután a számítások eredményeként létrejövő objektumot megkérjük, hogy futtassa le a + metódust c paraméterrel. Ez ekvivalens azzal, hogy:

(a.*(b)).+(c)

Mivel minden objektum, és mivel átdefiniálhatjuk a példánymetódusokat, mindig átdefiniálhatjuk az alap aritmetikát, ha nem tetszenek a kapott válaszok.

class Fixnum
  alias oldPlus +
  def +(other)
    oldPlus(other).succ
  end
end
1 + 2
»  4
a = 3
a += 4
»  8

Még hasznosabb maga a tény, hogy az általunk írt osztályok ugyanúgy szerepelhetnek operátor kifejezésekben, mint a beépített objektumok. Például, szeretnénk lefuttatni néhány másodpercnyi részletet a dal közepéből. Használhatnánk a [] operátort a lejátszott szám meghatározásához.

class Song
  def [](fromTime, toTime)
    result = Song.new(self.title + " [extract]",
      self.artist,
      toTime - fromTime)
      result.setStartTime(fromTime)
      result
  end
end

Ez a kódtöredék kiterjeszti a Song osztályt a [] metódussal, amely két paramétert kap (egy kezdő, és egy végpontot). Visszaad egy új dalt az adott intervallumra levágott számból. Ezután lejátszhatjuk ezt a bemutató részletet:

aSong[0, 0.15].play

Vegyes kifejezések

Az olyan egyértelmű operátor kifejezések, és metódushívások, a kevésbé egyértelmű utasítás kifejezések (mint például a már látott if, vagy case) mellett a Ruby rendelkezik még néhány dologgal, amit kifejezésekben használhatunk.

Parancs kifejezések

Ha ´ jelek közé zárunk egy string-et, vagy a kötetlen %-kal prefixelt formát használjuk, akkor az alapvetően az adott operációs rendszeren értelmezett parancsként fog végrehajtódni. A kifejezés értéke a parancs által az alapértelmezett kimenete. A sortörés jelek nem kerülnek eltávolításra.

`date`
»  "Sun Jun  9 00:08:26 CDT 2002\n"
`dir`.split[34]
»  "lib_singleton.tip"
%x{echo "Hello there"}
»  "Hello there\n"

Ezekben a parancs kifejezésekben használhatjuk a kiterjesztett kifejezéseket, és minden megszakító utasítást is.

for i in 0..3
status = `dbmanager status id=#{i}`
# ...
end

A parancs kilépési státuszát a $? globális változó tárolja.

A '`' lazasága

A parancs kifejezésének leírásában azt mondtuk, hogy alapvetően parancsként fog végrehajtódni. Valójában string-ként átadódik a Kernel::` metódusnak. Ezt pedig akár felül is definiálhatjuk.

alias oldBackquote `
def `(cmd)
  result = oldBackquote(cmd)
  if $? != 0
    raise "Command #{cmd} failed"
  end
  result
end
print `date`
print `data`

Eredménye:

Sun Jun  9 00:08:26 CDT 2002
prog.rb:3: command not found: data
prog.rb:5:in ``': Command data failed (RuntimeError)
  from prog.rb:10

Értékadás

Majd minden eddig megtekintett példa használt értékadást. Nos, nagyjából itt az ideje, hogy ejtsünk rólá néhány szót.

Egy értékadó utasítás egy - a bal oldalon szereplő - attribútum, vagy változó értékét (lvalue) állítja át úgy, hogy megegyezzen a jobb oldalon álló értékkel (rvalue). Ezután visszaadja az értéket az értékeadó kifejezés eredményeként. Következmény: az értékadásokat láncba fűzhetjük, valamint használhatunk értékadást viszonylag váratlan helyen is.

a = b = 1 + 2 + 3
a
»  6
b
»  6
a = (b = 1 + 2) + 3
a
»  6
b
»  3
File.open(name = gets.chomp)

Ruby-ban az értékadásoknak két alapvető formája van. Az első egy objektum referenciát rendel egy változóhoz, vagy konstanshoz. Az értékadás e formája bele van drótozva a nyelvbe.

instrument = "piano"
MIDDLE_A = 440

Az értékadás második formájában egy objektum attribútuma, vagy elem referenciája szerepel a bal oldalon.

aSong.duration    = 234
instrument["ano"] = "ccolo"

Ezek a formák speciálisak, mert a bal oldalon metódushívások szerepelnek, ami azt jelenti, hogy akár felül is definiálhatjuk őket.

Már láttuk, hogy definiálunk egy írható objektum attribútumot. Egyszerűen definiálunk egy metódust, melynek nevének végén egy egyenlőségjel szerepel. A metódus paraméterként az értékadás jobb oldalán szereplő értéket kapja.

class Song
  def duration=(newDuration)
    @duration = newDuration
  end
end

Nincs okunk arra, hogy ezeket az attribútum állító metódusokat összhangban tartsuk a belső példányváltozókkal, vagy hogy minden attribútum olvasóhoz adjunk attribútum író metódust is (vagy fordítva).

class Amplifier
  def volume=(newVolume)
    self.leftChannel = self.rightChannel = newVolume
  end
  # ...
end

Miért írtunk self.leftChannel-t a példában? Van itt egy kis trükk az írható attribútumokkal. Általábon az egy osztályon szereplő metódusok hívhatják az ugyanazon osztályon - vagy ősosztályon - belül található metódusokat funkcionális formában (azaz a self objektumot adva meg fogadóként). Ez azonban nem működik az attribútum írókkal. A Ruby meglátja az értékadást, és úgy dönt, hogy a bal oldalon szereplő névnek egy lokális változónak kell lennie, nem pedig egy attribútum íróra vonatkozó metódushívásnak.

class BrokenAmplifier
  attr_accessor :leftChannel, :rightChannel
  def volume=(vol)
    leftChannel = self.rightChannel = vol
  end
end
ba = BrokenAmplifier.new
ba.leftChannel = ba.rightChannel = 99
ba.volume = 5
ba.leftChannel
»  99
ba.rightChannel
»  5

Elfelejtettük kitenni a self-et az leftChannel értékadásánál, tehát a Ruby egy új, a volume= metódusban található lokális változóban tárolta el az értéket. Az objektum attribútuma azonban nem kerül frissítésre.

Párhuzamos értékadás

Programozási kurzusunk első hete alatt (vagy a második szemeszter, ha laza suliról van szó :)), biztosan írnunk kellett olyan programot, amely két változóban szereplő értéket cserél meg.

int a = 1;
int b = 2;
int temp;
temp = a;
a = b;
b = temp;

Ezt Rubyban sokkal egyszerűbben is megtehetjük:

a,b = b,a

A Ruby értékadások hatékonyak párhuzamosan is, tehát a hozzárendelt értékeket nem értinti maga az értékadás. A jobb oldalon szereplő értékek abban a sorrendben értékelődnek ki, amelyben megjelennek, még mielőtt a bal oldalon szereplő attribútumokbon bármilyen értékadást végrehajtanánk. Egy másik példát is megtekintünk. a második sor az a, b, és c változókhoz rendeli az x, x+=1, x+=1 értékeket.

x = 0
»  0
a, b, c   =   x, (x += 1), (x += 1)
»  [0, 1, 2]

Amikor egy értékadás bal oldalán több, mint egy érték szerepel, akkor az értékadás viszatérési értéke a jobb oldalon szereplő értékek alkotta tömb. Ha egy értékadás bal oldalán több érték szerepel, mint a jobb oldalon, akkor a többletet jelentő baloldai értékek nil értéket kapnak. Ha egy párhuzamos értékadásban több jobb oldali érték szerepel, mint bal oldali, akkor a fölösleges értékeket figyelmen kívül hagyjuk. Ha az értékadás bal oldalán csak egy érték szerepel, a jobb oldalon pedig több, akkor azok egy tömbként értékelődnek ki.

A Ruby párhuzamos értékadás operátorát használva kiterjeszthetünk, vagy épp korlátozhatunk tömböket. Ha az utolsó bal oldali érték egy *-gal kezdődik, akkor minden maradék jobb oldali értéket - egy tömbbe gyűjtve - kapja értékül. Hasonlóképp, ha az utolsó jobb oldali utolsó térkép egy tömb, és egy *-gal kezdődik, akkor azt helyben kibontja elemeire. (Ez nem szükséges, ha a jobb oldalon egyedül egy tömb szerepel. Ekkor ez automatikusan megtörténik.)

a = [1, 2, 3, 4]
b,  c = a
»  b == 1,   c == 2
b, *c = a
»  b == 1,  c == [2, 3, 4]
b,  c = 99,  a
»  b == 99,  c == [1, 2, 3, 4]
b, *c = 99,  a
»  b == 99,  c == [[1, 2, 3, 4]]
b,  c = 99, *a
»  b == 99,  c == 1
b, *c = 99, *a
»  b == 99,  c == [1, 2, 3, 4]

Beágyazott (nested) értékadások

A párhuzamos értékadásoknak van még egy kihangsúlyozandó előnye. A bal oldalon szerepelhet zárójelezett kifejezés. A Ruby úgy kezeli ezeket, mintha beágyazott értékadó utasítások lennének. Kitömöríti a kapcsolódó jobb oldali értéket, majd hozzárendeli azt a zárójelezett kifejezésekhez, mielőtt folytatná az értékadás magasabb szintű végrehajtását.

b, (c, d), e = 1,2,3,4
»  b == 1,    c == 2,    d == nil,    e == 3
b, (c, d), e = [1,2,3,4]
»  b == 1,   c == 2,   d == nil,   e == 3
b, (c, d), e = 1,[2,3],4
»  b == 1,   c == 2,   d == 3,   e == 4
b, (c, d), e = 1,[2,3,4],5
»  b == 1,   c == 2,   d == 3,   e == 5
b, (c,*d), e = 1,[2,3,4],5
»  b == 1,   c == 2,   d == [3, 4],   e == 5

Az értékadás más formái

Sok más nyelvhez hasonlóan a Ruby-ban is megtalálható egy szintaktikus rövidítés: a=a+2 írható a+=2 formában is.

A Ruby a második formát visszaalakítja az elsőre. Azaz a saját osztályainkban metódusként definiált operátoraink a vártnak megfelelően működnek.

class Bowdlerize
  def initialize(aString)
    @value = aString.gsub(/[aeiou]/, '*')
  end
  def +(other)
    Bowdlerize.new(self.to_s + other.to_s)
  end
  def to_s
    @value
  end
end
a = Bowdlerize.new("damn ")
»  d*mn
a += "shame"
»  d*mn sh*m*

Feltételes végrehajtás

A Ruby-ban több különböző feltételes végrehajtást támogató mechanizmus is található. Nagyrészük ismerős lesz, de egy részük elsőre furcsának tűnhet. Mielőtt fejest ugranánk ezekbe, töltsünk kis időt a boolean (logikai) kifejezésekkel.

Logikai kifejezések

A Ruby igen egyszerű igazság-fogalommal rendelkezik. Minden érték, ami nem nil, vagy a konstans false, az igaz. Megfigyelhetjük, hogy a könyvtárakban található rutinok igen következetesen használják ezt. Például az IO#gets, ami egy file következő sorát adja vissza, nil ad a file végénél, ami lehetővé teszi a következő ciklus megírását:

while line = gets
  # sor feldolgozása
end

Habár, itt van egy kis csapda a C, C++, és Perl programozók számára. A 0 (nulla) szám nem hamisként értékelődik. Még csak nem is egy nulla hosszúságú string-ként. Ezt a megszokást nehéz lehet levetkőzni...

Defined?, And, Or, és Not

A Ruby támogatja az összes normális operátort, valamint bemutat egy újat is, amelynek neve: defined?.

Mind az and, mind az &&, akkor ad igaz értéket, ha mindkét operandusa igaz. Csak akkor értékelik ki a második operandust, ha az első igaz. Az egyetlen különbség a kettő között a precedenciában található: and alacsonyabb szintű, mint a &&.

Hasonlóképp, mind az or, mind az || igaz értéket ad vissza, ha legalább az egyik operandus igaz. Csak akkor értékeli ki a második paramétert, ha az első hamis. A különbség: or alacsonyabb precedenciájú, mint az ||.

Hogy érdekesebbé tegyük a dolgot, az and és az or egymással megegyező precedenciájú, ellenben az && magasabb precedenciájú, mint az ||.

A not, és a ! operátorok az operandusok ellentettjét adják vissza (hamis, ha az operandus igaz, valamint igaz, ha az operandus hamis). És igen, a not és a ! csak a precedenciában különbözik.

A defined? operátor nil-t ad vissza, ha az argumentuma (ami akár egy kifejezés lehet) nem értelmezett, egyébként pedig visszaadja az argumentum leírását. Ha az argumentum a yield, akkor a yield string-et adja vissza, ha létezik az aktuális környezethez kapcsolt blokk.

defined? 1
»  "expression"
defined? dummy
»  nil
defined? printf
»  "method"
defined? String
»  "constant"
defined? $&
»  nil
defined? $_
»  "global-variable"
defined? Math::PI
»  "constant"
defined? ( c,d = 1,2 )
»  "assignment"
defined? 42.abs
»  "method"

A logikai operátorokhoz kapcsolódóan, a Ruby objektumok támogatják az ==, ===, <=>, =~, eql?, és equal? operátorokkal való összehasonlítást. Mind - kivéve az <=> operátort - az Object osztályban van definiálva, ám gyakran felülírjuk őket, az adott szemantikának megfelelően. Például, az Array osztály átdefiniálja az == operátort, tehát két tömb akkor egyenlő, ha ugyanannyi elemük van, és a kapcsolódó elemek megegyeznek.

Általános összehasonlító operátorok
Operátor Jelentés
== Érték szerinti egyenlőség-vizsgálat.
=== Egy "case" utasítás "when" kifejezésében egyenlőség-vizsgálat.
<=> Általános összehasonlítás. -1-et, 0-át, +1-et ad vissza, attól függően, hogy a fogadója kisebb, egyenlő, vagy nagyobb, mint az argumentuma.
<, <=, >=, > A kisebb, kisebb-egyenlő, nagyobb-egyenlő, nagyobb operátorok.
=~ Reguláris kifejezés mintaillesztés.
eql? Igaz, ha mind a fogadó, mind az argumentum megegyező típusú. Például 1 == 1.0 igaz, de 1.eql?(1.0) hamis.
equal? Igaz, ha a fogadónak, és az argumentumnak megegyezik az objektum azonosítója (object id)

Az ==-nek és az =~-nek létezik negált formája, azaz !=, !~. Habár ezeket a Ruby a program olvasásakor átkonvertálja, tehát a!=b ekvivalens !(a==b)-vel, valamint a!~b ekvivalens !(a=~b)-vel. Ez azt jelenti, hogy ha írunk egy az ==, és az =~ operátorokat felüldefiniáló osztályt, akkor egyből kapjuk a megfelelő !=, !~ operátorokat is, nem kell őket definiálni.

A Ruby intervallumokat használhatjuk logikai kifejezésekként. Egy exp1..exp2 intervallum hamis lesz, amíg exp1 igazzá nem válik. Ezután igazként értékelődik ki, amíg exp2 igazzá nem válik. Ezután az intervallum újraindul, és kezdődik minden előlről.

Végül, használhatunk egy reguláris kifejezést logikai kifejezésként. A Ruby átfogalmazza $_ = ~/re/ formára.

Az If, és az Unless kifejezések

Egy Ruby if kifejezés nagyon hasonló más nyelvek if utasításaihoz.

if aSong.artist == "Gillespie" then
  handle = "Dizzy"
elsif aSong.artist == "Parker" then
  handle = "Bird"
else
  handle = "unknown"
end

Ha az if utasításainkat több sorba rendezzük, a then kulcsszót el is hagyhatjuk.

if aSong.artist == "Gillespie"
  handle = "Dizzy"
elsif aSong.artist == "Parker"
  handle = "Bird"
else
  handle = "unknown"
end

Habár, ha szorosabban rendezzük a kódot, a then kulcsszót muszáj kitenni, hogy elkülönítsük a logiaki kifejezést az őt követő utasítástól.

if aSong.artist == "Gillespie" then  handle = "Dizzy"
elsif aSong.artist == "Parker" then  handle = "Bird"
else  handle = "unknown"
end

Lehet 0, vagy több elsif kifejezésünk, és egy opcionális else kifejezésünk.

Mint azt már említettük, az if valójában egy kifejezés, nem pedig utasítás - értéket ad vissza. Nem kell feltétlen felhasználnunk ezt az értéket, ám néha hasznos lehet.

handle =   if aSong.artist == "Gillespie" then
      "Dizzy"
    elsif aSong.artist == "Parker" then
      "Bird"
    else
      "unknown"
    end

A Ruby-ban megtalálható az if negált formája is. Ez pedig az unless.

unless aSong.duration > 180 then
  cost = .25
else
  cost = .35
end

Végül, a C rajongóknak, a Ruby támogatja a C formájú feltételes kifejezéseket is.

cost = aSong.duration > 180 ? .35 : .25

A feltételes kifejezés vagy a kettőspont előtti, vagy az utáni értéket adja vissza, attól függően a kérdőjel előtti logikai kifejezés igaz, vagy hamis voltától. Ebben az esetben, ha a szám hosszabb, mint 3 perc, a kifejezés .35-öt ad vissza, rövidebb számok esetén pedig .25-öt. Bármelyik is az eredmény, azt a cost változó tárolja.

If és unless módosítók

A Ruby tartalmazza a Perl egyik elegáns funkcióját. Az utasítás módosítók lehetővé teszik a feltételes állítások normál utasítások mögé helyezését.

mon, day, year = $1, $2, $3 if /(\d\d)-(\d\d)-(\d\d)/
puts "a = #{a}" if fDebug
print total unless total == 0

Egy if módosítót megelőző kifejezés csak akkor értékelődik ki, ha a feltétel igaz. Az unless pont ellenkezőleg működik.

while gets
  next if /^#/            # megjegyzések átugrása
  parseLine unless /^$/   # Ne elemezzünk üres sorokat
end

Mivel az if egy kifejezés, írhatunk nagyon homályos utasításokat is, mint például:

if artist == "John Coltrane"
  artist = "'Trane"
end unless nicknames == "no"

Ez az út az őrület kapujához vezet.

Case kifejezések

A Ruby case kifejezése nagyon erőteljes eszköz: egy többágú if.

case inputLine
  when "debug"
    dumpDebugInfo
    dumpSymbols
  when /p\s+(\w+)/
    dumpVariable($1)
  when "quit", "exit"
    exit
  else
    print "Illegal command: #{inputLine}"
end

Mint az if utasításoknál, a case is értéket ad vissza, mégpedig az utolsó kiértékelt kifejezését, és szintén szükséges a then kulcsszó kiírása, ha a kifejezéseket a feltételekkel egy sorba írjuk.

kind = case year
    when 1850..1889 then "Blues"
    when 1890..1909 then "Ragtime"
    when 1910..1929 then "New Orleans Jazz"
    when 1930..1939 then "Swing"
    when 1940..1950 then "Bebop"
    else                 "Jazz"
       end

A case működése során összehasonlítja a célkifejezést (a case kulcsszó után találhatót) minden when kulcsszó után található kifejezéssel. Ez az összehasonlítás az === operátor használatával történik. Mindaddig, amíg egy osztály értelmes === operátorral rendelkezik - és minden beépített osztály rendelkezik ilyennel -, az adott osztály objektumai használhatók ilyen esetszétválasztós kifejezésekben.

Például a reguláris kifejezések az === operátort egyszerű mintaillesztéssel definiálják.

case line
  when /title=(.*)/
    puts "Title is #$1"
  when /track=(.*)/
    puts "Track is #$1"
  when /artist=(.*)/
    puts "Artist is #$1"
end

A Ruby osztályok a Class osztály példányai, amely definiál egy === operátort annak ellenőrzésére, hogy az argumentum az osztály, vagy annak valamely ősosztályának példánya-e? Tehát (mellőzve a többszörös öröklődés előnyeit) tesztelhetjük az osztályok objektumait:

case shape
  when Square, Rectangle
    # ...
  when Circle
    # ...
  when Triangle
    # ...
  else
    # ...
end

Ciklusok

El ne áruljuk senkinek, de a Ruby beépítettet ciklus-szerkezetei nagyon primitívek.

A while 0, vagy több alkalommal futtatja le a törzsében található kódot, mindaddig, amíg a feltétele igaz. Például, a következő kifejezés addig olvas, amíg az bement el nem fogy.

while gets
  # ...
end

Ennek van egy negált formája is, amely addig futtatja törzsét, amíg a feltétel igaz nem lesz.

until playList.duration > 60
  playList.add(songList.pop)
end

Az if-hez és unless-hez hasonlóan, mindkét ciklus lehet utasítás módosító is.

a *= 2 while a < 100
a -= 10 until a < 100

A logikai kifejezéseknél említettük, hogy az intervallumokat használhatjuk egyfajta ugrándozásra is, igazat adva vissza, amikor egy esemény igazzá válik, és mindaddig igazat visszaadni, amíg egy másik esemény igazzá nem válik. Ezt általában ciklusokban is használhatjuk. A következő példában egy szöveget tartalmazó file-ból beolvassuk az első 10 számot, mégpedig betűvel leírva (első, második, ...), de csak azokat írjuk ki, amelyekben megtalálható a harmadik, negyedik, ötödik.

file = File.open("ordinal")
while file.gets
  print  if /third/ .. /fifth/
end

Eredménye:

harmadik
negyedik
ötödik

Egy logikai kifejezésben használt intervallum elemei önmaguk is lehetnek kifejezések. Ezek minden alkalommal kiértékelődnek, amikor a nagy logikai kifejezés kiértékelődik. Például a következő program kihasználja, hogy a $. változó az aktuális bemeneti sor számát tárolja.

file = File.open("ordinal")
while file.gets
  print if ($. == 1) || /nyol/ .. ($. == 3) || /kil/
end

Eredménye:

első
második
harmadik
nyolcadik
kilencedik

Van egy kis szépséghibája a while, uncle utasítások módosítására használatának. Ha egy begin/end blokkot módosítanak: a kód legalább egyszer végrehajtódik, a logikai kifejezés értékétől függetlenül.

print "Hello\n" while false
begin
  print "Goodbye\n"
end while false

Eredmény:

Goodbye

Iterátorok (bejárók)

Ha elolvassuk az előző fejezet elejét, kicsit elbátortalanodhatunk. A Ruby beépítettet ciklus-szerkezetei nagyon primitívek. Azért ne csüggedjünk, van jó hír is. A Ruby nem igényel semmiféle bonyolult beépített ciklusokra, mert minden mókás dolgot a Ruby iterátorokkal valósít meg.

Például, a Ruby-ban nincs for ciklus - legalábbis nem olyan, amit Java-ban, C-ben, vagy C++-ban találunk. Ehelyett a Ruby olyan beépített osztályokban található metódusokat használ, amelyek ezzel ekvivalens, de kisebb hibavalószínűségű működést garantálnak.

Nézzünk meg néhány példát.

3.times do
print "Ho! "
end

Eredménye:

Ho! Ho! Ho!

Így könnyű elkerülni a szakállas, és eggyel-elcsúszok hibákat - ez a ciklus egymás után háromszor kerül végrehajtásra. A times-zal kapcsolatban: az egészek speciális intervallumokon belül is tudnak ciklusokat létrehozni, a downto, az upto, és a step használatával.Például a hagyományos 0-tól 9-ig tartó for ciklus (valami i=0; i < 10; i++ féle) a következőképp írható:

0.upto(9) do |x|
  print x, " "
end

Eredménye:

0 1 2 3 4 5 6 7 8 9

Egy 0-tól 12-ig hármassával léptető ciklus a következőképp néz ki:

0.step(12, 3) {|x| print x, " " }

Eredménye:

0 3 6 9 12

Hasonlóképp, egy tömb elemein (vagy más tárolók elemein) való lépkedés az each metódus használatával igen egyszerű:

[ 1, 1, 2, 3, 5 ].each {|val| print val, " " }

Eredménye:

1 1 2 3 5

Azaz, ha egy osztály rendelkezik each metódussal, az Enumerable (felsorolható) modulban található további metódusok is elérhetővé válnak. Például, a File osztály rendelkezik each metódussal, amely a file minden egyes sorát visszaadja lépésenként. Az Enumerable modulban található grep metódus használatával végigjárhatjuk csak azokat a sorokat, amelyek megfelelnek bizonyos feltételeknek.

File.open("ordinal").grep /n$/ do |line|
  print line
end

Eredménye:

negyedik
nyolcadik
kilencedik

És végül, és valószínűleg utóljára, a Ruby legegyszerűbb ciklusa következik. Ez pedig a loop.

loop {
  # block ...
}

A loop örökké hívogatja a kapcsolódó blokkot (vagy amíg meg nem szakítjuk a ciklust, de ahhoz, hogy megtudjuk, hogyan kell, tovább kell olvasni. :))

For... in

Korábban azt mondtuk, hogy a Ruby beépített primitív ciklusai a while, és az until. Akkor mit is csinál a for dolog? Nos, ő olyan, mint egy szintaktikus kockacukor. Amikor azt írjuk:

for aSong in songList
  aSong.play
end

A Ruby ezt így érti:

songList.each do |aSong|
  aSong.play
end

A for és az each közötti egyetlen különbség a törzsükben definiált lokális változók láthatóságában van.

A for használható minden olyan objektumon, amely rendelkezik each metódussal, mint például egy tömb, vagy egy intervallum.

for i in ['fee', 'fi', 'fo', 'fum']
  print i, " "
end
for i in 1..3
  print i, " "
end
for i in File.open("ordinal").find_all { |l| l =~ /d$/}
  print i.chomp, " "
end

Eredménye:

fee fi fo fum 1 2 3 second third

Mindaddig, amíg az osztályunk rendelkezik each metódussal, addig a for ciklussal bejárhatjuk.

class Periods
  def each
    yield "Classical"
    yield "Jazz"
    yield "Rock"
  end
end
periods = Periods.new
for genre in periods
  print genre, " "
end

Eredménye:

Classical Jazz Rock

Break, redo és next

A ciklusvezérlés tartalmaz break, redo, és next utasításokat, amelyek lehető teszik a normális ciklikus viselkedéstől való eltérést.

A break megszakítja, és azonnal bezárja a az adott ciklust, a program pedig a ciklus blokkját követő első utasítással folytatódik. A redo újrakezdi a ciklust a feltétel újrakiértékelése, vagy a következő elem generálása nélkül. A next a ciklus végére ugrik, és kezdi a következő elem feldolgozását.

while gets
  next if /^\s*#/
# megjegyzések átugrása
  break if /^END/
# végén megállás
  redo if gsub!(/`(.*?)`/) { eval($1) }
# sor feldolgozása
end

Ezek a kulcsszavak iterátor alapú ciklusoknál is használhatók:

i=0
loop do
  i += 1
  next if i < 3
  print i
  break if i > 4
end

Eredménye:

345

Retry

A redo utasítás a ciklus aktuális menetének megismétlését hajtja végre. Néha azonban szükség van a teljes ciklus megismétlésére, a legelejétől. Erre való a retry utasítás, amely bármilyen iterátor ciklust újrakezd.

for i in 1..100
  print "Now at #{i}. Restart? "
  retry if gets =~ /^y/i
end

Interaktív futtatás esetén a következőt láthatjuk.

Now at 1. Restart? n
Now at 2. Restart? y
Now at 1. Restart? n
 . . .

A retry újra kiértékeli az iterátor összes argumentumát az újraindítás előtt. Az online Ruby dokumentáció a következő csináld-magad amíg ciklust hozza példának.

def doUntil(cond)
  yield
  retry unless cond
end
i = 0
doUntil(i > 3) {
  print i, " "
  i += 1
}

Eredménye:

0 1 2 3 4

Változók láthatósága, és a ciklusok

A while, az until, és a for ciklusok beépített ciklusok, és nem tartalmaznak semmi újdonságot a láthatósággal kapcsolatban - az előzőleg létezett lokális változik használhatók a ciklus törzsében, és minden új lokális változó elérhető utánna is.

Az iterátorok -mint a loop, és az each - által használt blokkok már kicsit másak. Általában az ezen blokkokon belül létrehozott változók nem érhetők el a blokkon kívül.

[ 1, 2, 3 ].each do |x|
  y = x + 1
end
[ x, y ]

Eredménye:

prog.rb:4: undefined local variable or method `x'
for #<Object:0x401c2ce0> (NameError)

Habár, ha a blokk futásának ideje alatt már létezik egy lokális változó az adott blokkban szereplő névvel, akkor a már létező lokális változót használja a blokk. Azaz a blokk vége után az értéke elérhető marad. Ahogy a következő példa is mutatja, ez érvényes a blokkon belüli normális változókra, és a blokk paraméterére is.

x = nil
y = nil
[ 1, 2, 3 ].each do |x|
  y = x + 1
end
[ x, y ]
»  [3, 4]

< Előző oldalKövetkező oldal >