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.)
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.
get string - henter en tekststreng ifra standard input.
to integer - forsøker å gjøre objektet om til et heltall.
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 |
fjerner et eventuelt newline-tegn i fra slutten av strengen.
gjør store bokstaver om til små.
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.)
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") |
gir indeksen til hvor i strengen det gitte argumentet finnes eller nil om det ikke finnes.
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.
De to viktigste datastrukturene i Ruby er lister (Array) og hashmaps (Hash).
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 ('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 |
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.
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.
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)
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.
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." |