Programmering i Ruby

Den Pragmatiske Programmerers Veiledning

Forrige < Innhold ^
Neste >

Uttrykk



Inntill når har vi vært ganske nonchalant i vår bruk av Ruby uttrykk. Ting som a=b+c er temmelig vanlig, tross alt. Du kunne ha skrivet haugevis med Ruby-kode uten å lese noe som helst av dette kapittelet.

Men da hadde det ikke vært så moro ;-).

En av de første forskjellene mellom Ruby og andre språk som stikker seg ut, er at alt som kan returnere en verdi på en fornuftig måte, gjør det: så å si alt er et uttrykk. Hvilke praktisk betydninger har dette?

Blandt de mest åpenbare, er muligheten til å kjede uttrykk sammen.

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

Litt mindre åpenbart, er det at det som vanligvis er setninger i C eller Java, er uttrykk i Ruby. For eksempel så returnerer både if og case-setninger verdien til det siste uttrykket som evalueres.

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

Vi går nærmere inn på if og case-setninger på side 79(??).

Operator uttrykk

Ruby har de grunnleggende operatorene ((+, -, *, /, og så videre) samt noen få overraskelser. En fullstendig liste over operatorene og deres presedens finnes i tabell 18.4 på side 219(??).

Mesteparten av operatorene i Ruby er faktisk metodekall. Når du skriver a*b+c ber du objektet som a peker til om å utføre metoden ``*'', med b som parameter. Deretter ber du objektet som returneres av det forrige metodekallet om å utføre ``+'' hvor c er parameter til dette neste metodekallet. Dette er identisk med å skrive

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

Siden alle ting er objekter og du kan endre instansmetoder, kan du alltids omdefinere grunnleggende aritmetikk dersom du ikke liker svarene du får.

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

Litt mer nyttig er det faktum at klasser du skriver kan ta del i operator-uttrykk slik som innebygde objekter. Vi ønsker for eksempel å ta tak i en noen sekunder med lyd i fra inni en sang. Vi kunne bruke indekseringsoperatoren ``[]'' for å spesifiere lydbiten som skal hentes ut.

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

Denne kodebiten utvider Song klassen med ``[]'' metoden som tar to parametre (et starttidspunkt og et slutttidspunkt). Den returnerer en ny sang, med musikken klippet i henhold til det gitte intervallet. Vi kunne dermed avspilt begynnelsen på en sang med følgende kode:

aSong[0, 0.15].play

Ymse uttrykk

I tillegg til de åpenbare operatoruttrykkene og metodekallene, og de (muligens) litt mindre åpenbare setningsuttrykkene (som if og case), har Ruby et par ting til som du kan bruke i uttrykk.

Utførelse av kommandoer

Dersom du angir en streng i bakoverlente siteringstegn (backquotes), eller bruker skilletegnsformen med %x foran, vil teksten (vanligvis) bli utført av det underliggende operativsystemet som en kommando. Verdien som returneres fra uttrykket er standard utput fra kommandoen. Linjeskifttegn fjernes ikke for deg, så det du får tilbake vil inneholde retur- eller linjematingstegn på enden av linjen(e).

`date` » "Sun Nov 25 23:43:12 CST 2001\n"
`dir`.split[34] » "lib_singleton.tip"
%x{echo "Hello there"} » "Hello there\n"

Du kan bruke interpolering av uttrykk og de vanlige escape-sekvensene inne i kommandostrengen.

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

Utgangsstatusen til kommandoen legges i den globale variabelen $?.

Backquotes er "myke"

I beskrivelsen av kommandouttrykket, sa vi at strengen i backquotes ville ``vanligvis'' bli utført som en kommando. Faktum er at strengen sendes til metoden kalt Kernel::` (et enkelt bakoverlent siteringstegn). Du kan overstyre dette om du ønsker.

alias oldBackquote `
def `(cmd)
  result = oldBackquote(cmd)
  if $? != 0
    raise "Command #{cmd} failed"
  end
  result
end
print `date`
print `data`
produserer:
Sun Nov 25 23:43:12 CST 2001
prog.rb:3: command not found: data
prog.rb:5:in ``': Command data failed (RuntimeError)
	from prog.rb:10

Tilordning

Nesten alle eksemplene vi har vist hittil har innehold tilordning. Kanskje det er på tide å si ta det for oss.

En tilordningssetning setter variabelen eller attributet på venstre side (lvalue eller l-verdi) til å referere til verdien på høyre side (rvalue eller r-verdi). Deretter returnerer den verdien av tilordningen, slik at du kan kjede tilordningen sammen, samt gjøre tilordninger i noen litt merkelige steder.

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

Det er to typer tilordning i Ruby. Den første tilordner en objektreferanse til en variabel eller konstant. Denne typen er hardkodet i språket.

instrument = "piano"
MIDDLE_A   = 440

Den andre formen har vi når man tilordner til en objektattributt eller elementreferanse.

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

Disse siste måtene å tilordne på er spesielle, siden de implementeres ved å kalle metoder i det som er på venstre side, hvilket betyr at du kan overstyre dem.

Vi har alt sett hvordan vi kan definere et skrivbart objektattributt. Vi definerer bare et metodenavn som har et er-lik tegn på slutten. Denne metoden mottar som parameter det som var på høyresiden av tilordningen.

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

Disse metodene for å sette attributter trenger ikke å korrespondere med interne instansvariabler. Det må ikke være en metode for å lese et attributt til hver metode for å skrive et attributt (eller motsatt).

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

Litt på siden: Bruk av metoder for tilgang til attributter i en klasse

Du lurer kanskje på hvorfor vi skrev self.leftChannel i eksempelet på side 74(??). Det er en liten felle med skrivbare attributter. Vanligvis kan metoder i en klasse kalle andre metoder i samme klasse og superklassene på den funksjonelle måten (det vil si at mottakeren implisitt er self). Men dette fungerer ikke med metoder som skriver attributter. Ruby ser tilordningen og antar at navnet på venstre side må være en lokal variabel i stedet for et metodekall til en attributtskriver.
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

Vi glemte å skrive ``self.'' foran tilordningen til leftChannel, og dermed lagret Ruby den nye verdien i en lokal variabel i volume= metoden; objektets attributt ble aldri oppdatert. Denne typen feil kan være vanskelig å finne ut av.

Parallell tilordning

I løpet av den første uken i et programmeringskurs (eller i andre semester, dersom det var mye festing), fikk du muligens beskjed om å skrive kode som bytter verdiene som er i to variabler:

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

temp = a; a = b; b = temp;

Dette kan gjøres mye enklere i Ruby:

a, b = b, a

Tilordninger i Ruby kjøres for alle praktiske formål i parallell, slik at verdiene som blir tilordnet blir ikke påvirket av selve tilordningen. Verdiene på høyre side evauleres i den rekkefølgen de listes opp i før noen av variablene eller attributtene på venstre side blir tilordnet. Et litt oppkonstruert eksempel viser dette. Andre linje tilordner til variablene a, b og c verdiene fra uttrykkene x, x+=1, and x+=1.

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

Når en tilordning har mer enn en l-verdi, vil hele tilordningsuttrykket returnere en tabell av alle r-verdiene. Dersom en tilordning har flere l-verdier enn r-verdier, vil de øvrige l-verdiene bli satt til nil. Dersom en tilordning med mer enn en l-verdi samtidig har flere r-verdier enn l-verdier, vil de ekstra r-verdiene ignoreres. Fra og med Ruby versjon 1.6.2 vil en tilordning med bare en l-verdi og flere r-verdier føre til at r-verdiene pakkes inn i en tabell og tilordnes den ene l-verdien.

Du kan pakke sammen og utvide tabeller ved å bruke parallell tilordning. Dersom den siste l-verdien har en stjerne foran seg, vil alle de resterende r-verdiene samles i denne som en tabell. Likeså dersom den siste r-verdien er en Array, kan du sette en stjerne foran den med den effekt at innholdet pakkes ut som individuelle r-verdier. (Dette skjer automatisk dersom r-verdien er det eneste på høyresiden.)

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]

Nøstede tilordninger

Parallelle tilordninger har nok en egenskap verdt å ta for oss. Venstresiden kan inneholde en liste av termer i parenteser. Ruby tar for seg disse termene som om de var en nøstet tilordningssetning. Ruby henter ut en r-verdi, tilordner den til termene i parentesen, og går så videre til tilordningen på nivået over.

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

Andre typer tilordning

Som mange andre språk, har Ruby litt syntaktisk sukker: a=a+2 kan skrives som a+=2.

Den andre, korte formen omformes internt til den første. Dette betyr at operatorer du har definert i egne klasser fungerer som forventet.

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*

Betinget utførelse

Ruby har flere forskjellige mekanismer for å gjøre betinget utførelse av kode; mesteparten vil føles kjent og flere av dem har noen kjekke, små forskjeller. Men før vi begynner på disse, trenger vi en liten titt på boolske uttrykk.

Boolske uttrykk

Ruby har en veldig enkel definisjon på sannhet. Enhver verdi som ikke er nil eller konstanten false, er sann. Du vil etterhvert merke at bibliotekrutinene bruker dette konsekvent. For eksempel returnerer IO#gets vanligvis neste linje fra en fil, men den gir ut nil på slutten, slik at du kan skrive løkker som denne:

while line = gets
  # prosesser en linje
end

Men der er også en stor potensiell fallgruve for C, C++ og Perl programmerere. Tallet null blir ikke ansett som en usann verdi. Ikke en tom streng heller. Dette kan være gamle vaner som er vonde å vende seg av med.

Defined?, and, or og not

Ruby har alle de vanlige boolske operatorene og introduserer en ny operator defined?.

Både ``and'' og ``&&'' evalueres til sant kun dersom begge operandene er sanne. Begge evalurerer bare den første operanden dersom den første er sann (dette kalles gjerne for ``kortsluttet evaluering''). Den eneste forskjellen på disse to formene er rangeringen (``and'' binder svakere enn ``&&'', som har forrang).

Likeledes evalurerer både ``or'' og ``||'' til sant hvis en av operandene deres er sanne. Den andre operanden evalueres kun dersom den første er usann. Akkurat som for ``and'', er rangeringen den eneste forskjellen mellom ``or'' og ``||''.

For å krydre hverdagen og gjøre den interessant, så har ``and'' og ``or'' samme presedens, mens ``&&'' rangerer over ``||''.

``not'' og ``!'' gir det motsatte av operatoren de gis (usann hvis operanden er sann og sann hvis operanden er usann). Jada, eneste forskjellen mellom ``not'' og ``!'' er rangeringen.

Alle reglene rundt hvilke operatorer som rangerer over hvilke, finner du et sammendrag av i tabell 18.4 på side 219(??).

Operatoren defined? svarer med nil hvis argumentet du sender til den (som kan være et vilkårlig uttrykk) ikke er definert, ellers returnerer den en beskrivelse av argumentet. Dersom argumentet er yield og det er en kodeblokk metodekallet vi er inne i, vil defined? returnere strengen ``yield''.

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"

I tillegg til de boolske operatorene har Ruby objekter også støtte for sammenligning ved hjelp av metodene ==, ===, <=>, =~, eql?, og equal? (se tabell 7.1 på side 79(??)). Alle unntatt <=> er definert i Object klassen, men overstyres ofte av subklasser for å tilby fornuftig oppførsel. For eksempel så redefinerer Array klassen == slik at du tabellobjekter er like hvis de har like mange elementer og elementene med samme indeks er like.

Vanlige sammenligningsoperatorer
Operator Betydning
== Tester om verdi er lik.
=== Brukes for å teste likhet i when-delen(e) i en case-setning.
<=> Generell sammenligningsoperator. Returnerer -1, 0, eller +1, alt etter om mottakeren er mindre, lik eller større en argumentet.
<, <=, >=, > Sammenligningsoperatorer for mindre enn, mindre eller lik, større eller lik og større enn.
=~ Mønstergjenkjenning ved hjelp av regulære uttrykk.
eql? Sann dersom mottaker og argumentet både har samme type og verdi. 1 == 1.0 returnerer true, men 1.eql?(1.0) returnerer false.
equal? Sann dersom mottaker og argumentet har samme objektidentifikator.

Både == og =~ har negative versjoner, != og !~. Men disse konverteres når programmet ditt leses. a!=b er ekvivalent med !(a==b), og a!~b er akkurat det samme som !(a=~b). Dette betyr at om du skriver en klasse som overstyrer == eller =~ får du != og !~ gratis. Men dette betyr også at du ikke kan definere != og !~ uavhengig av == og =~.

Du kan bruke en Range som et boolsk uttrykk. En rekke slik som exp1..exp2 vil evaluere til usant inntill exp1 blir sant. Deretter vil rekken evaluere til sant helt til exp2 blir sant, for da resetter rekken seg og venter igjen at exp1 skal slå til. Vi viser noen eksempler på dette på side 82(??).

Sist, men ikke minst, kan du også bruke et regulært uttrykk alene som et boolsk uttrykk. Ruby fortolker det som $_=~/re/.

Uttrykkene if og unless

Et if-uttrykk i Ruby er ganske lik ``if''-setninger i andre språk.

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

Dersom du skriver if-setningen over flere linjer, kan du droppe nøkkelordet then.

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

Men dersom du skriver mer kompakt kode, vil nøkkelordet then være nødvendig for å skille mellom det boolske uttrykket og de påfølgende setningen(e).

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

Du kan ha null eller flere elsif-klausuler samt en valgfri else-klausul i en if-setning.

Vi har tidligere nevnt at if er et uttrykk, og ikke en setning---fordi den returnerer en verdi. Du må ikke bruke verdien som kommer ut av et if-uttrykk, men du kan få bruk for den.

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

Ruby har også en negativ variant av if-setningen:

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

Til slutt, til forlystelse for C tilhengerene, har Ruby også betingede uttrykk i samme stil som C:

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

Det betingede uttrykket returnerer enten verdien av uttrykket før eller etter kolon, alt etter om det boolske uttrykket før spørsmålstegnet er true eller false. I dette tilfellet returneres .35 dersom sangen varer lengre enn 3 minutter, og .25 for kortere sanger. Uansett resultat, så tilordnes det til cost.

Modifikatorene if og unless

Ruby deler en kjekk egenskap med Perl. Setningsmodifikatorer tillater deg å sette logiske betingelser på slutten av en vanlig setning.

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

Modifikatoren if gjør at det uttrykket foran kun vil bli evaluert dersom betingelsen er sann. unless fungerer motsatt.

while gets
  next if /^#/            # Skip comments
  parseLine unless /^$/   # Don't parse empty lines
end

Siden if i seg selv er et uttrykk, kan du klare å skrive setninger som er vanskelig å forstå, slik som:

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

Denne veien fører til galskap.

Case uttrykk

I Ruby er case-uttrykket et kraftig verktøy: en if med flere forgreininger og ekstra muskler.

case inputLine

  when "debug"     dumpDebugInfo     dumpSymbols

  when /p\s+(\w+)/     dumpVariable($1)

  when "quit", "exit"     exit

  else     print "Illegal command: #{inputLine}" end

Akkurat som if returnerer case verdien til det siste uttrykket som evalueres, samt at du trenger then nøkkelordet hvis uttrykket er på samme linje som betingelsen.

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

case sammenligner målet (uttrykket etter nøkkelordet case) med hver av sammenligningsuttrykkene etter when nøkkelordene. Denne sammenligningen gjøres som sammenligningsuttrykk === mål. Så lenge en klasse definerer fornuftig semantikk for === (slik alle de innebygde klassene gjør), kan objekter av den klassen brukes i case uttrykk.

For eksempel kan regulære uttrykk definere === som en enkel mønstergjenkjenning.

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

Klasser er i Ruby instanser av klassen Class, som definerer === som en test for å se om argumentet er en instans av klassen eller en av dens superklasser. Det vil si at du (ved å gi opp polymorfi og påkalle deg vreden til OO gudene) kan teste hvilken klasse objektet er instans av:

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

Løkker

Ikke si det til noen, men Ruby sine innebygde løkkekonstruksjoner er temmelig primitive.

while-løkken utfører kodekroppen sin null eller flere ganger, alt etter hvor lenge betingelsen er sann. For eksempel så leser denne vanlige vendingen inn fra innput til det er tomt.

while gets
  # ...
end

Det finnes også en variant som er en negasjon av while, og i stedet går i løkke til betingelsen blir sann.

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

I likhet med if og unless, kan begge disse løkkekonstruksjonene også brukes som setningsmodifikatorer.

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

På side 78(??) i delen om boolske uttrykk nevnte vi at en Range kan brukes som en slags flip-flop, som returnerer sant når en hendelse skjer og forblir sann til en annen hendelse tar sted. Denne egenskapen blir normalt brukt i løkker. I det følgende eksempelet, leser vi inn en tekstfil som inneholder de første ti numrene. (``first,'' ``second,'' og så videre) men vi skriver bare ut linjene fra og med den som inneholder ``third'' og til og med den som inneholder ``fifth.''

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

Elementene i en Range i et boolsk uttrykk kan igjen være uttrykk. Disse evalueres hver gang hele det boolske uttrykket evalueres. For eksempel så bruker den følgende koden det faktum at variabelen $. holder nummeret på hvilken linje vi er på i innputstrømmen for å vise linjenummerne en til og med tre, samt de mellom linjer som inneholder /eig/ og /nin/.

file = File.open("ordinal")
while file.gets
  print if ($. == 1) || /eig/ .. ($. == 3) || /nin/
end
produserer:
first
second
third
eighth
ninth

Det er et lite problem med å bruke while og until som setningsmodifikatorer. Hvis setningen de modifiserer er en begin/end blokk, vil koden i blokken alltid kjøres en gang, uansett verdien av det boolske uttrykket.

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

Iteratorer

Du ble kanskje skuffet når du leste begynnelsen på forrige del. ``Ruby sine innebygde løkkekonstruksjoner er temmelig primitive.'' Bli ikke frustrert, kjære leser, for vi har gode nyheter. Ruby trenger ikke noen sofistikerte innebygde løkker, da alt det gøyale er implementert ved hjelp av iteratorer.

For eksempel, så har ikke Ruby en ``for''-løkke---i allefall ikke den typen du finner i C, C++ og Java. Ruby bruker i stedet metoder definert i forskjellige innebygde klasser til å gi oss samme, men mer feiltolerant, funksjonalitet.

La oss se på noen eksempler.

3.times do
  print "Ho! "
end
produserer:
Ho! Ho! Ho!

Det er enkelt å unngå problemer med grensetilfeller og "off-by-1" feil; denne løkken utføres tre ganger. Basta. I tillegg til times, kan heltall gå i løkke over spesifikke rekker ved å kalle downto, upto og step. En tradisjonell ``for''-løkke som går fra 0 til 9 (noe slik som i=0; i < 10; i++) kan for eksempel skrives slik:

0.upto(9) do |x|
  print x, " "
end
produserer:
0 1 2 3 4 5 6 7 8 9

En løkke fra 0 til 12 med steglengde 3 kan skrives slik:

0.step(12, 3) {|x| print x, " " }
produserer:
0 3 6 9 12

På samme måte er det enkelt å iterere over tabeller og andre beholdere ved å bruke each metoden deres.

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

Og når en klasse først støtter each, blir alle de øvrige metodene i Enumerable modulen (dokumentasjonen starter på side 403(??) og et sammendrag på side 102(??)) tilgjengelige. For eksempel så tilbyr File klassen en each metode som returnerer hver linje fra en fil i tur og orden. Ved hjelp av grep metoden i Enumerable, kunne vi iterert over kun de linjene som oppfylte en gitt betingelse.

File.open("ordinal").grep /d$/ do |line|
  print line
end
produserer:
second
third

Sist, og sannsynligvis minst, er den enkleste løkkekonstruksjonen av dem alle. Ruby har en innebygd iterator som heter loop.

loop {
  # blokk ...
}

Iteratoren loop kaller den tilknyttede blokken for evig og alltid (eller iallefall inntill du eksplisitt bryter ut av løkken, men du må lese litt fremover for å finne ut hvordan du gjør det).

For ... In

Tidligere sa vi at de innebygde, primitive løkkekonstruksjonene var while and until. Hva er denne ``for'' tingen da? Det viser seg at for omtrent er en syntaktisk sukkerklump. Når du skriver

for aSong in songList
  aSong.play
end

oversetter Ruby det til noe slik som:

songList.each do |aSong|
  aSong.play
end

Den eneste forskjellen mellom for-løkken og each-metoden er skopet til lokale variable som defineres i kroppen. Dette diskuteres på side 87(??).

Du kan bruke for til å iterere over et hvert objekt som svarer på each, slik som en Array eller en Range.

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
produserer:
fee fi fo fum 1 2 3 second third

Så forutsatt at klassen din definerer en fornuftig each, kan du bruke for-løkker til å itere over innholdet.

class Periods
  def each
    yield "Classical"
    yield "Jazz"
    yield "Rock"
  end
end

periods = Periods.new for genre in periods   print genre, " " end
produserer:
Classical Jazz Rock

Break, redo og next

Kontrollkonstruksjonene for løkker, break, redo og next, lar deg manipulere den vanlige flyten igjennom en løkke eller iterator.

break avslutter umiddelbart den omliggende løkken; kontrolflyten fortsetter rett etter blokken. redo gjentar løkken fra starten av, men uten å evaluere betingelsen på nytt eller å hente det neste elementet (i en iterator). next hopper til slutten på løkken og rent praktisk starter neste iterasjon.

while gets
  next if /^\s*#/   # hopp over kommentarer
  break if /^END/   # stopp ved slutten
                    # bytt ut ting i backquotes og prøv på nytt
  redo if gsub!(/`(.*?)`/) { eval($1) }
  # prosesser linjen ...
end

Disse nøkkelordene kan også brukes sammen med hvilken som helst iterator-basert løkkemekanisme:

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

Retry

redo setningen får løkken til å gjenta iterasjonen. Men noen ganger ønsker du å skru hele løkken tilbake til begynnelsen. Løsningen er retry, som starter en vilkårlig iterator-løkke på nytt.

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

Kjør dette interaktivt, og du vil se noe slik

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

retry reevaluerer eventuelle argumenter til iteratoren før den starter den på nytt. Ruby dokumentasjonen på nettet har følgende eksempel på en gjør-det-selv until-løkke.

def doUntil(cond)
  yield
  retry unless cond
end

i = 0 doUntil(i > 3) {   print i, " "   i += 1 }
produserer:
0 1 2 3 4

Variabelskop og løkker

while, until og for-løkkene er innebygt i språket og introduserer ikke et nytt skop; lokale variabler som fantes før løkken, kan brukes inne i løkken, og nye lokale variabler som lages i løkken, vil være tilgjengelig utenifra etterpå.

Blokkene som iteratorene bruker (slik som loop og each) er litt anderledes. Vanligvis vil lokale variabler som blir laget i disse blokkene ikke tilgjengelig på utsiden av blokken.

[ 1, 2, 3 ].each do |x|
  y = x + 1
end
[ x, y ]
produserer:
prog.rb:4: undefined local variable or method `x'
for #<Object:0x401c0ce0> (NameError)

Men dersom det allerede finnes en lokal variabel med samme navn som en variabel i blokken når blokken kjøres, vil den eksisterende lokale variabelen brukes. Verdien som blokken har lagret i den vil være tilgjengelig etter at blokken er ferdig utført. Som det neste eksempelet viser gjelder dette både for vanlige variabler i blokken og blokkens parametre.

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

( In progress translation to Norwegian by NorwayRUG. $Revision: 1.10 $ )


Forrige < Innhold ^
Neste >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide".
Translation to norwegian by Norway Ruby User Group.
Copyright for the english original authored by David Thomas and Andrew Hunt:
Copyright © 2001 Addison Wesley Longman, Inc.
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at
http://www.opencontent.org/openpub/).

(Please note that the license for the original has changed from the above. The above is the license of the original version that was used as a foundation for the translation efforts.)

Copyright for the norwegian translation:
Copyright © 2002 Norway Ruby User Group.
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at
http://www.opencontent.org/openpub/).
Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.
Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.