Next Previous Contents

3. Flytkontroll

Logiske uttrykk skiller seg fra en del andre språk på et viktig område:
Kun nil og false evalueres til usant.
0, "", [], {} og andre 'tomme' objekter evaluerer til sant.

3.1 Hvis, dersom...

  1| # Spør først om alderen.
  2| print "Hvor gammel er du?: "
  3| alder = gets.to_i
  4| 
  5| if alder < 1 then puts "Nå tuller du vel?"; exit end
  6| 
  7| if alder >= 18
  8|   puts "Du er myndig."
  9| elsif alder >= 16
 10|   puts "Du er lovlig."
 11| else
 12|   puts "Småen!"
 13| end
 14| 
 15| # 'if' kan også returnere en verdi.
 16| drikkevare = 
 17|   if alder >= 60
 18|     "Sviskejuice"
 19|   else
 20|     if alder >= 20
 21|       "Sprit"
 22|     elsif alder >= 18
 23|       "Øl og vin"
 24|     else
 25|       "Brus"
 26|     end	       
 27|   end
 28| puts "Kjøp deg litt #{drikkevare}"

Merk at then nøkkelordet ikke er nødvendig når man skriver if-setningen over flere linjer. Noen ting er valgfrie i Ruby forutsatt at uttrykket ikke blir tvetydig.

gets

get string - henter en tekststreng ifra standard input.

to_i

to integer - forsøker å gjøre objektet om til et heltall.

Forutsatt, med mindre...

Mulighetene til å skrive logiske uttrykk som er nærmere hvordan vi snakker, gjør Ruby mer lesbart, men kan også forvirre.

  1| print "Liker du Ruby? [ja/nei]:"
  2| svar = gets.chomp.downcase 
  3| 
  4| puts "Jeg liker også Ruby!" if svar=="ja"
  5| 
  6| puts "La oss kode litt Ruby." unless svar=="nei"
  7| 
  8| unless svar[0] == ?j
  9|   puts "Mener du at du ikke liker Ruby?"
 10| else
 11|   puts "Doble negasjoner er forvirrende..."
 12| end

?j

tallverdien til tegnet 'j'

chomp fjerner et eventuelt newline-tegn i fra slutten av strengen. downcase gjør store bokstaver om til små.

Case

Ruby har også case-konstruksjonen, som ofte er et bedre valg enn en rekke elsif'er mot samme variabel.

  1| print "Er du gutt eller jente?: "
  2| svar = gets.downcase.chomp
  3| 
  4| # case er også kjent som switch/case i andre språk
  5| case svar
  6| when "intetkjønn" 
  7|   puts "Hei!"
  8| when "jente", "kvinne", "dame"
  9|   puts "Heisann søta!"
 10| when "gutt", "mann", "herre"
 11|   puts "Heisann kjekken!"
 12| else 
 13|   puts "God dag herr/fru?"
 14| end

Legg merke til at en when blokk kan slå ut på flere oppgitte verdier. Man kan også bruke regulære uttrykk, Range-objekter, klasser etc. Du kan også lage dine egne objekter som kan brukes her ved å implementere === operatoren, som kalles både "relationship operator" og "case equality operator". (Ja, det er 3 likhetstegn.)

3.2 Løkker - while

Hvis ikke du er fra Bergen, så har du vel falt for denne spøken en gang...

  1| # while - gjenta så lenge noe er sant
  2| begin
  3|   print "En gutt og ei jente satt i ett tre. \n"
  4|   print "Så falt gutten ned. \n"
  5|   print "  Hvem satt igjen? :> "
  6|   svar = gets.chomp.downcase
  7| end while svar.index("jenta")

index

gir indeksen til hvor i strengen det gitte argumentet finnes eller nil om det ikke finnes.

Until

  1| # Litt mer gøy med matte.
  2| a = nil
  3| 
  4| # until - gjenta inntil noe blir sant
  5| until a == 0
  6|   print "Skriv inn ett tall (0 avslutter): "
  7|   a = gets.to_i
  8|   puts "#{a} opphøyd i 2      = " + (a**2).to_s
  9|   puts "Kvaderatroten av #{a} = " + (Math.sqrt(a)).to_s 
 10| end

Merk at når løkke-uttrykket er foran koden som skal gjentas, droppes begin-nøkkelordet.

3.3 Iterasjon

For-løkken finnes i Ruby, men er egentlig bare syntaktisk sukker for en betydelig kraftigere mekanisme.

  1| # La oss skrive ut 3-gange-tabellen
  2| tall = 3
  3| 
  4| # Ruby har for-løkker som de fleste språk
  5| for i in (1..10)
  6|   puts "#{i} gange #{tall} er #{i*tall}"
  7| end
  8| 
  9| tall = 5
 10| 
 11| # for-løkken over er syntaktisk sukker for
 12| # følgende bruk av iterator-metoden each.
 13| (1..10).each do |i|
 14|   puts "#{i} gange #{tall} er #{i*tall}"
 15| end  

(1..10) lager et Range-objekt, som spenner i fra og med 1, til og med 10. Dersom du ikke ønsker å inkludere 10, kan du bruke 3 punktum, for eksempel så spenner (1...10) i fra 1, til og med 9.

Iterere over en datastruktur

Når man skal iterere over datastrukturer, blir indekser lett i veien. Såkalte "off-by-one" feil er ganske vanlige. Men hvorfor ikke la datastrukturen stå for itereringen?

  1| personer = [ "Ola", "Per", "Jan", "Line"]
  2| 
  3| for i in (0...personer.size)         # Den "gamle" naive måten.
  4|   puts "Hei " + personer[i]
  5| end
  6| 
  7| for person in personer               # Alle objekter som implementerer 
  8|   puts "Er #{person} tilstede?"      # 'each' kan itereres over.
  9| end
 10| 
 11| personer.each do |person|            # Ruby-måten.
 12|   puts "Velkommen #{person}"         
 13| end  

"Hva er den |person|greia?"
Det er nesten som en argumentdeklarasjon, men ikke til en funksjon.
do |person| end er en blokk, et veldig viktig konsept i Ruby.

Yield og blokker.

En blokk er en kodebit, som kan motta argumenter og returnere en verdi. Der slutter likhetene med en metode.

En blokk holder også tak i den omliggende konteksten og bindingen. Det betyr at lokale variable er tilgjengelig i blokk-koden, noe som gjør den perfekt til callback, f.eks. i grafiske brukergrensesnitt.

  1| # En enkel, naiv iterator metode.
  2| def tell_fingre
  3|   yield "Tommel"
  4|   yield "Peke"
  5|   yield "Lange"
  6|   yield "Ringe"
  7|   yield "Lille"
  8| end
  9| 
 10| # Blocken har tilgang til lokale variabler.
 11| postfix = "finger..."
 12| 
 13| # Vi sender med en block når vi kaller iterator-metoden.
 14| tell_fingre do |finger|
 15|   puts finger + postfix
 16| end

En blokk er ikke et objekt av effektivitetshensyn, men kan innkapsles i et Proc-objekt. (Via Proc.new, nøkkelordene proc og lambda, eller via bruk av &-prefikset i argumentlista til metodedefinisjonen)

Blokker for håndtering av ressurser

Minnehåndtering i Ruby ordnes ved garbage collection, men en del andre ressurser krever eksplisitt lukking. Åpne filer, databasetilkoblinger og andre ressurser som tar opp mer enn minne, har det bedre med en eksplisitt lukking. Men slikt glemmer man lett...

  1| # Hent filnavn fra kommandolinjen.
  2| filnavn = ARGV[0]
  3| 
  4| # Gamle måten.
  5| fil = File.open( filnavn, "r" )
  6| linjenummer = 0
  7| fil.readlines.each{ |linje|
  8|   linjenummer +=1
  9|   print "#{linjenummer}: #{linje}" 
 10| }
 11| fil.close   # Lukker filen eksplisitt
 12| 
 13| # Bruk block til ressurs styring.
 14| File.open( filnavn, "r") { |fil|
 15|   linjenummer = 0
 16|   fil.readlines.each{ |linje|
 17|     linjenummer += 1
 18|     print "#{linjenummer}>: #{linje}" 
 19|   }
 20| } 
 21| # File.open lukker filen etter å ha kjørt koden i blocken.

Her benytter vi en annen måte for å angi blokker. I stedet for do |arg| ... end bruker vi {|arg| ... }. Varianten med krøllparenteser har høyere presedens.

Proc-objekter

Et Proc-objekt innkapsler en blokk. De kan lages via Proc.new, nøkkelordene proc og lambda, eller via bruk av &-prefikset i argumentlista til metodedefinisjonen.

  1| # Lager ett Proc-objekt av en block.
  2| p = Proc.new {|i| 
  3|   puts "Hei #{i}!" 
  4| }
  5| 
  6| # Vi kan kalle Proc'en eksplisitt...
  7| p.call('Jens') #=> "Hei Jens!"
  8| 
  9| # Bruke den som block...
 10| [1,2,3].each( &p ) 
 11| 
 12| # & prefikset gjør en evt. blokk om til et Proc-objekt.
 13| def tar_block( a, &block )
 14|   block.call( a )
 15| end
 16| 
 17| tar_block(5){|b| 
 18|   puts  "Hallo #{b}."
 19| } #=> "Hallo 5."


Next Previous Contents