|
|||
Forrige < |
Innhold ^
|
Neste >
|
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]
|
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 |
if
og case
-setninger på side 79(??).
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) |
class Fixnum
|
||
alias oldPlus +
|
||
def +(other)
|
||
oldPlus(other).succ
|
||
end
|
||
end
|
||
|
||
1 + 2
|
» |
4
|
a = 3
|
||
a += 4
|
» |
8
|
[]
'' 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 |
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 |
if
og
case
), har Ruby et par ting til som du kan bruke i uttrykk.
%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"
|
for i in 0..3 status = `dbmanager status id=#{i}` # ... end |
$?
.
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` |
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 |
a = b = 1 + 2 + 3
|
||
a
|
» |
6
|
b
|
» |
6
|
a = (b = 1 + 2) + 3
|
||
a
|
» |
6
|
b
|
» |
3
|
File.open(name = gets.chomp)
|
instrument = "piano" MIDDLE_A = 440 |
aSong.duration = 234 instrument["ano"] = "ccolo" |
class Song def duration=(newDuration) @duration = newDuration end end |
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.
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.
|
int a = 1; int b = 2; int temp; temp = a; a = b; b = temp; |
a, b = b, a |
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]
|
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] |
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 |
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*
|
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 |
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"
|
==
, ===
, <=>
, =~
, 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
|
==
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/
.
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 |
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 |
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 |
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 |
if
-setningen:
unless aSong.duration > 180 then cost = .25 else cost = .35 end |
cost = aSong.duration > 180 ? .35 : .25 |
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
.
mon, day, year = $1, $2, $3 if /(\d\d)-(\d\d)-(\d\d)/ puts "a = #{a}" if fDebug print total unless total == 0 |
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 |
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" |
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 |
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 |
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 |
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 |
while
, og i stedet går i løkke til betingelsen blir sann.
until playList.duration > 60 playList.add(songList.pop) end |
if
og unless
,
kan begge disse løkkekonstruksjonene også brukes som setningsmodifikatorer.
a *= 2 while a < 100 a -= 10 until a < 100 |
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 |
third fourth fifth |
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 |
first second third eighth ninth |
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 |
Goodbye |
3.times do print "Ho! " end |
Ho! Ho! Ho! |
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 |
0 1 2 3 4 5 6 7 8 9 |
0.step(12, 3) {|x| print x, " " } |
0 3 6 9 12 |
each
metoden deres.
[ 1, 1, 2, 3, 5 ].each {|val| print val, " " } |
1 1 2 3 5 |
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 |
second third |
loop
.
loop { # blokk ... } |
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).
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 |
songList.each do |aSong| aSong.play end |
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 |
fee fi fo fum 1 2 3 second third |
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 |
Classical Jazz Rock |
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 |
i=0 loop do i += 1 next if i < 3 print i break if i > 4 end |
345 |
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 |
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 } |
0 1 2 3 4 |
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 ] |
prog.rb:4: undefined local variable or method `x' |
x = nil
|
||
y = nil
|
||
[ 1, 2, 3 ].each do |x|
|
||
y = x + 1
|
||
end
|
||
[ x, y ]
|
» |
[3, 4]
|
Forrige < |
Innhold ^
|
Neste >
|