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.
(For folk vant til C, Perl og/eller Python, er dette en av de vanligste kildene til feil.)

3.1 Hvis, dersom...

If-setningen byr ikke på så mange overraskelser i Ruby.

  1| # Spør først om alderen.
  2| print "Hvor gammel er du?: "
  3| alder = gets.to_i
  4| 
  5| if alder < 3 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, så du slipper
 16| # å bruke "ternary" (? : ) operatoren hvis du ikke liker den.
 17| drikkevare = 
 18|   if alder >= 60
 19|     "Sviskejuice"
 20|   else
 21|     if alder >= 20
 22|       "Sprit"
 23|     elsif alder >= 18
 24|       "Øl og vin"
 25|     else
 26|       "Brus"
 27|     end	       
 28|   end
 29| 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| # if kan også brukes etter uttrykk
  5| puts "Jeg liker også Ruby!" if svar=="ja"
  6| 
  7| # 'unless' er det motsatte av 'if'
  8| puts "La oss kode litt Ruby." unless svar=="nei"
  9| 
 10| # men bør brukes forsiktig
 11| unless svar[0] == ?j
 12|   puts "Mener du at du ikke liker Ruby?"
 13| else
 14|   puts "Doble negasjoner er forvirrende..."
 15| end

chomp

fjerner et eventuelt newline-tegn i fra slutten av strengen.

downcase

gjør store bokstaver om til små.

Merk at chomp og downcase ikke har noen '!', så de returnerer kopier som har blitt modifisert. De endrer ikke objektet de blir kalt på.

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 opprinnelig, 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 Datastrukturer

De to viktigste datastrukturene i Ruby er lister (Array) og hashmaps (Hash).

Array

En lineær liste med objekter av allverdens typer

  1| # Lister
  2| fisketyper = ['torsk', 'laks', 'ørret', 'sei']
  3| 
  4| pizza  = Array.new
  5| pizza.push 'pepperoni'    # Legge til elementer i listen:
  6| pizza.unshift 'ananas'
  7| pizza << :chorizo
  8| pizza += %w{kylling biff kjøttboller}   # Slå sammen to lister
  9| 
 10| # Manipulering
 11| puts fisketyper.collect{|fisk| "Jeg liker ikke " + fisk }.join(" ei!\n")
 12| 
 13| pizza.delete('Ananas')
 14| puts "Jeg vil ha en pizza med #{pizza.join(', ')} og masse ost!"

Hash

Hash ('ordbok'?), også kjent som 'map', 'dictionary' i andre språk.

  1| # Hash map / dictionary
  2| ordbok = {
  3|   'ruby'    => 'Edelsten eller språk',
  4|   'python'  => 'Reptil eller språk',
  5|   :pi       => 'Matematisk symbol',
  6|   alpha:    'Gresk symbol for bokstaven A',
  7| }
  8| 
  9| # Oppslag 
 10| ordbok['ruby']
 11| ordbok[:alpha]
 12| ordbok[:beta]   #=> nil # fordi nøkkelen ikke finnes
 13| ordbok[:pi]
 14| ordbok['pi']    #=> nil # fordi Symbol og String er forskjellig
 15| 
 16| ordbok['python'] = 'Mener du som i Monty Python?'   # Overskriver
 17| ordbok[:e] = Math::E    # Legger til
 18| ordbok[:pi] ||= 3.142   # Overskrives ikke, da den allerede finnes
 19| 
 20| tekst = ordbok.map do |ord, beskrivelse|
 21|   "Ordet '#{ord}' er definert som: #{beskrivelse}." if beskrivelse
 22| end.compact.join("\n")
 23| puts tekst

Om nøkkelen ikke finnes, returnes 'nil'

  1| # Nøsting av hash
  2| treet = {
  3|   rot: {
  4|     stamme: {
  5|       grener: [
  6|         {blader: 'grønne', knopper: 'spirende'},
  7|         {blader: 'gule'},
  8|         {blader: 'røde',   bladlus: 'døende'},
  9|         {blader: 'brune'},
 10|         {blader: false, insekter: nil},
 11|         {snø: 'kald'},
 12|       ]
 13|     }
 14|   }
 15| }
 16| 
 17| puts "Bladene på treet har gjennom året"
 18| treet[:rot][:stamme][:grener].each do |gren|
 19|   dikt = gren.collect do |key, verdi|
 20|     if verdi
 21|       "#{verdi} #{key}"
 22|     else
 23|       "ingen #{key}"
 24|     end
 25|   end.compact.join(' med ')
 26|   puts dikt
 27| end

3.4 Iterasjon - Iterator-pattern og 'yield'

For løkken finnes fremdeles i Ruby, men hvor Python har gjort for-løkken glupere, har Ruby gått videre og tatt i bruk Enumertor-pattern'et.

  1| # La oss skrive ut 7-gange-tabellen
  2| tall = 7
  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| # 5-gange-tabellen
 10| tall = 5
 11| 
 12| # men for-løkkens dager er talte. 
 13| # for-løkken over er syntaktisk sukker for
 14| # følgende bruk av iterator-metoden each.
 15| (1..10).each do |i|
 16|   puts "#{i} gange #{tall} er #{i*tall}"
 17| 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| # Den "gamle" naive måten
  4| for i in (0...personer.size) 
  5|   puts "Hei " + personer[i]
  6| end
  7| 
  8| # Alle objekter som implementerer 'each' kan itereres over
  9| for person in personer
 10|   puts "Er #{person} tilstede?"
 11| end
 12| 
 13| # Ruby-måten: Bruke Iterator og en block
 14| personer.each do |person|
 15|   puts "Velkommen #{person}"
 16| end  

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

Yield og blocks.

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

En block holder også tak i den omliggende konteksten og bindingen. Det betyr at lokale variable er tilgjengelig i block-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 block 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)

Blocks 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") do |fil|
 15|   linjenummer = 0
 16|   fil.readlines.each do |linje|
 17|     linjenummer += 1
 18|     print "#{linjenummer}>: #{linje}" 
 19|   end
 20| end
 21| # File.open lukker filen etter å ha  kjørt koden i blokken.

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

Proc-objekter

Et Proc-objekt innkapsler som oftest en block. 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 do |i| 
  3|   puts "Hei #{i}!" 
  4| end
  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. block om til et Proc-objekt.
 13| def tar_block(a, &block)
 14|   block.call(a)
 15| end
 16| 
 17| tar_block(5) do |b| 
 18|   puts  "Hallo #{b}."
 19| end #=> "Hallo 5."


Next Previous Contents