Programmering i Ruby

Den Pragmatiske Programmerers Veiledning

Forrige < Innhold ^
Neste >

Mer om metoder



Andre språk har funksjoner, prosedyrer, metoder og rutiner, men i Ruby finnes kun metoden---en samling uttrykk som returnerer en verdi.

Inntill nå har vi i denne boka definert og brukt metoder uten å tenke særlig på det. Nå er det på tide å titte på de bakenforliggende detaljene.

Definere en metode

Som vi har sett tidligere, defineres en metode ved hjelp av nøkkelordet def. Navn på metoder bør begynne med en liten forbokstav. [ Du får ikke umiddelbart en feil hvis du bruker en stor forbokstav, men når Ruby ser deg kalle metoden, vil den først gjette seg til at det er en konstant, og ikke et metode kall. Dette kan resultere i at Ruby tolker kallet feil. ] Metoder som spør om noe, gis ofte et navn med et ``?''-tegn på slutten, som for eksempel instance_of?. Metoder som er ``farlige'' eller endrer objektet de kalles på, kan få et navn som slutter med ``!''. Et eksempel er String som både har en chop og en chop! metode. Den første returnerer en modifisert streng, mens den andre modifiserer selve mottakerstrengen. ``?'' og ``!'' er de eneste merkelige tegnene som metoder kan ha som suffiks.

Når vi har fått angitt et navn til vår nye metode, trenger vi kanksje å deklarere noen parametre. Vi skriver bare en liste av lokale variabelnavn i mellom parenteser. Noen eksempler på metodedeklarasjoner:

def myNewMethod(arg1, arg2, arg3)     # 3 argumenter
  # Koden til den nye metoden skrives her
end

def myOtherNewMethod                  # Ingen argumenter   # Koden til den nye metoden skrives her end

Ruby tillater deg å angi defaultverdier for argumenter til en metode---verdier som brukes dersom den som kaller metoden ikke gir inn disse parametrene eksplisitt. Dette gjøres med tilordningsoperatoren.

def coolDude(arg1="Miles", arg2="Coltrane", arg3="Roach")
  "#{arg1}, #{arg2}, #{arg3}."
end
coolDude » "Miles, Coltrane, Roach."
coolDude("Bart") » "Bart, Coltrane, Roach."
coolDude("Bart", "Elwood") » "Bart, Elwood, Roach."
coolDude("Bart", "Elwood", "Linus") » "Bart, Elwood, Linus."

Metodekroppen inneholder normale Ruby uttrykk, med unntak av du kan ikke kan definere en instansmetode, klasse eller modul inne i metoden. Metodens returverdi er verdien til det siste uttrykket som utføres, eller resultatet av et eksplisitt return uttrykk.

Argumentlister med varierende lengde

Men hva om du ønsker å sende inn forskjellige antall argumenter eller samle flere argumenter inn i en enkelt parameter? Dette kan du få til ved å putte en stjerne foran navnet på parameteren, etter alle de ``vanlige'' parameterne.

def varargs(arg1, *rest)
  "Got #{arg1} and #{rest.join(', ')}"
end
varargs("one") » "Got one and "
varargs("one", "two") » "Got one and two"
varargs "one", "two", "three" » "Got one and two, three"

I dette eksempelet tilordnes de første argumentet til den første parameteren i metoden, som vanlig. Men, siden den neste parameteren har en stjerne foran seg, blir alle de gjenværende argumentene samlet i en ny Array, som så tilordnes til den parameteren.

Metoder og blokker

Som nevnt tidligere når vi omtalte blokker og iteratorer på side 38(??), så kan en metode assosieres med en blokk når den kalles. Vanligvis kaller du bare blokken fra innsiden av metoden ved å bruke yield.

def takeBlock(p1)
  if block_given?
    yield(p1)
  else
    p1
  end
end

takeBlock("no block") » "no block"
takeBlock("no block") { |s| s.sub(/no /, '') } » "block"

Hvis den siste parameteren i en metodedefinisjon derimot prefikses med et og-tegn (``&''), bli blokken som assosieres med metodekallet omgjort til et Proc-objekt, som så tilordnes til den parameteren.

class TaxCalculator
  def initialize(name, &block)
    @name, @block = name, block
  end
  def getTax(amount)
    "#@name on #{amount} = #{ @block.call(amount) }"
  end
end
tc = TaxCalculator.new("Sales tax") { |amt| amt * 0.075 }
tc.getTax(100) » "Sales tax on 100 = 7.5"
tc.getTax(250) » "Sales tax on 250 = 18.75"

Kalle en metode

Du kaller en metode ved å angi en mottaker, metodens navn og eventuelt noen parametre og en blokk.

connection.downloadMP3("jitterbug") { |p| showProgress(p) }

I dette eksempelet er connection mottakeren, downloadMP3 er navnet til metoden, "jitterbug" er en parameter og greiene mellom klammeparentesene er den tilknyttede blokken.

For klasse- eller modulmetoder, vil mottakeren være klasse- eller modulnavnet.

File.size("testfile")
Math.sin(Math::PI/4)

Hvis du dropper mottakeren, brukes self, det gjeldende objektet.

self.id » 537790064
id » 537790064
self.type » Object
type » Object

Denne default mekanismen bruker Ruby til å implementere private metoder. Private metoder kan ikke kalles med en mottaker, så de må være tilgjengelige i det gjeldende objektet.

De eventuelle parametrene kommer etter metodenavnet. Hvis det ikke blir tvetydig, kan du droppe parentesene rundt argumentlisten når du kaller metoden. [Annen Ruby dokumentasjon omtaler av og til disse metodekallene uten parenteser for ``kommandoer.''] Men vi anbefaler ikke dette, unntatt i de enkleste tilfellene---det er nemlig noen subtile problemer som kan dukke opp. [ Mer bestemt, du bruke parenteser på et metodekall som igjen er paramterer til et annet metodekall (med mindre det er den siste parameteren). ] Regelen vår er enkel: hvis du er i tvil, bruk parenteser.

a = obj.hash    # Same as
a = obj.hash()  # this.

obj.someMethod "Arg1", arg2, arg3   # Same thing as obj.someMethod("Arg1", arg2, arg3)  # with parentheses.

Pakke ut tabeller i metodekall

Tidligere så vi at ved å plassere en stjerne foran en formell parameter i en metodedefinisjon, så vil flere argumenter gitt i kallet bli samlet opp i en tabell. Dette fungerer også andre veien.

Når du kaller en metode kan du pakke opp en Array slik at hvert av elementene blir et eget argument. Dette gjøres ved å bruke samme stjernetegnet foran tabellargumentet (som må komme etter alle de vanlige argumentene).

def five(a, b, c, d, e)
  "I was passed #{a} #{b} #{c} #{d} #{e}"
end
five(1, 2, 3, 4, 5 ) » "I was passed 1 2 3 4 5"
five(1, 2, 3, *['a', 'b']) » "I was passed 1 2 3 a b"
five(*(10..14).to_a) » "I was passed 10 11 12 13 14"

Mer dynamisk bruk av blokker

Vi har alt sett hvordan du kan tilknytte en blokk til et metodekall.

listBones("aardvark") do |aBone|
  # ...
end

Vanligvis holder dette---du tilknytter en gitt kodeblokk til et metodekall på i stor grad den samme måten som du har en samling kodelinjer etter en if eller while-setning.

Men av og til ønsker du mer fleksibilitet. Som eksempel, hvis vi holder på å lære bort matematikk. [ Selvfølgelig ville Andy og Dave måtte lære seg matematikk først. Conrad Schneiker minnet oss om at det finnes tre typer mennesker: de som kan regne og de som ikke kan. ] En student kunne be om en n-pluss tabell eller en n-gange tabell. Hvis studenten ber om en tabell med to-gangen, ville vi printet ut 2, 4, 6, 8 og så videre. (Denne koden sjekker ikke innput for feil.)

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i

if times =~ /^t/   puts((1..10).collect { |n| n*number }.join(", ")) else   puts((1..10).collect { |n| n+number }.join(", ")) end
produserer:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

Dette fungerer, men er ikke pent, med nesten identisk kode på hver gren av if-setningen. Det ville vært kjekt om vi kunne faktorisere ut blokken som gjør utregningen.

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i

if times =~ /^t/   calc = proc { |n| n*number } else   calc = proc { |n| n+number } end puts((1..10).collect(&calc).join(", "))
produserer:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

Dersom det siste argumentet til en metode har et og-tegn foran, antar Ruby at det er ett Proc-objekt. Ruby fjerner det fra parameterlisten, gjør Proc-objektet om til en blokk og tilknytter den til metodekallet.

Denne teknikken kan også brukes til å strø litt syntaktisk sukker over blokkbruken din. For eksempel, noen ganger ønsker du å ta en iterator og lagre hver verdi den sender ut via yield og samle det i en tabell. Her gjenbruker vi Fibonacci nummergeneratoren fra side 40(??).

a = []
fibUpTo(20) { |val| a << val } » nil
a.inspect » "[1, 1, 2, 3, 5, 8, 13]"

Dette fungerer men intensjonen vår er ikke så klar som vi gjerne ønsker. I stedet vil vi definere en metode, into, som returnerer blokken som fyller tabellen. (Legg merke til at blokken som returneres egentlig er en tillukkning---den holder en referanse til parameteren anArray selv etter at into har returnert.)

def into(anArray)
  return proc { |val| anArray << val }
end
fibUpTo 20, &into(a = [])
a.inspect » "[1, 1, 2, 3, 5, 8, 13]"

Sanking av hashargumenter

Noen programmeringsspråk har ``nøkkelordsargumenter''---det vil si, i stedet for å gi et gitt antall argumenter i en gitt rekkefølge, sender du med navnet på argumentet sammen med verdien. Ruby 1.6 har ikke nøkkelordsargumenter (men det er planlagt implementert i Ruby 1.8).

Mens vi venter på dette funksjonaliteten, har folk begynt å bruke hashtabeller for å oppnå det samme. Vi kunne for eksempel vurdere å legge til en kraftigere søkemulighet til SongList klassen vår.

class SongList
  def createSearch(name, params)
    # ...
  end
end
aList.createSearch("short jazz songs", {
                   'genre'            => "jazz",
                   'durationLessThan' => 270
                   } )

Den første parameteren er navnet på søket og den andre er en litteral hashtabell som inneholder parametre for selve søket. Bruken av en Hash gjør at vi kan simulere nøkkelord: se etter sanger i ``jazz'' sjangeren og kortere varighet enn fire og et halvt minutt. Men denne framgangsmåten er litt kronglete, samt at klammeparentesene gjør at hashtabellen lett kan forveksles med en blokk. Derfor har har Ruby en snarvei. Du kan plassere nøkkel => verdi par i en argumentliste, forutsatt at de kommer etter normale parametre og før tabell- og blokkargumenter. Alle disse parene vil bli samlet inn i en enkelt hashtabell og sendt inn som et argument til metoden. Klammeparenteser er helt unødvendig.

aList.createSearch("short jazz songs",
                   'genre'            => "jazz",
                   'durationLessThan' => 270
                   )

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


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.