8. Pisanje vlastitih metoda

Kako smo mogli vidjeti, kružne petlje i upravljači (iteratori) dozvoljavaju nam da određene dijelove koda, odnosno programa, ponavaljamo željeni broj puta. Međutim, ponekad će biti potrebno da uradimo istu stvar nekoliko puta, ali na različitim mjestima unutar programa. Naprimjer: recimo da pišemo upitnik za studenta psihologije. Prema onome što sam dobio od studenata psihologije, upitnik bi vjerovatno izgledao ovako:

puts 'Hello, and thank you for taking the time to'

puts 'help me with this experiment. My experiment'

puts 'has to do with the way people feel about'

puts 'Mexican food. Just think about Mexican food'

puts 'and try to answer every question honestly,'

puts 'with either a "yes" or a "no". My experiment'

puts 'has nothing to do with bed-wetting.'

puts

# Postavljamo pitanja, ali zanemarujemo odgovore na njih.

goodAnswer = false

while (not goodAnswer)

puts 'Do you like eating tacos?'

answer = gets.chomp.downcase

if (answer == 'yes' or answer == 'no')

goodAnswer = true

else

puts 'Please answer "yes" or "no".'

end

end

goodAnswer = false

while (not goodAnswer)

puts 'Do you like eating burritos?'

answer = gets.chomp.downcase

if (answer == 'yes' or answer == 'no')

goodAnswer = true

else

puts 'Please answer "yes" or "no".'

end

end

# Međutim na „ovaj“ odgovor ćemo obratiti pažnju.

goodAnswer = false

while (not goodAnswer)

puts 'Do you wet the bed?'

answer = gets.chomp.downcase

if (answer == 'yes' or answer == 'no')

goodAnswer = true

if answer == 'yes'

wetsBed = true

else

wetsBed = false

end

else

puts 'Please answer "yes" or "no".'

end

end

goodAnswer = false

while (not goodAnswer)

puts 'Do you like eating chimichangas?'

answer = gets.chomp.downcase

if (answer == 'yes' or answer == 'no')

goodAnswer = true

else

puts 'Please answer "yes" or "no".'

end

end

puts 'Just a few more questions...'

goodAnswer = false

while (not goodAnswer)

puts 'Do you like eating sopapillas?'

answer = gets.chomp.downcase

if (answer == 'yes' or answer == 'no')

goodAnswer = true

else

puts 'Please answer "yes" or "no".'

end

end

# Postavite još neka pitanja, ako želite, na isti način.

puts

puts 'DEBRIEFING:'

puts 'Thank you for taking the time to help with'

puts 'this experiment. In fact, this experiment'

puts 'has nothing to do with Mexican food. It is'

puts 'an experiment about bed-wetting. The Mexican'

puts 'food was just there to catch you off guard'

puts 'in the hopes that you would answer more'

puts 'honestly. Thanks again.'

puts

puts wetsBed

Ispis:

Hello, and thank you for taking the time to

help me with this experiment. My experiment

has to do with the way people feel about

Mexican food. Just think about Mexican food

and try to answer every question honestly,

with either a "yes" or a "no". My experiment

has nothing to do with bed-wetting.

Do you like eating tacos?

yes

Do you like eating burritos?

yes

Do you wet the bed?

no way!

Please answer "yes" or "no".

Do you wet the bed?

NO

Do you like eating chimichangas?

yes

Just a few more questions...

Do you like eating sopapillas?

yes

DEBRIEFING:

Thank you for taking the time to help with

this experiment. In fact, this experiment

has nothing to do with Mexican food. It is

an experiment about bed-wetting. The Mexican

food was just there to catch you off guard

in the hopes that you would answer more

honestly. Thanks again.

false

Bijaše to prilično dug program, sa puno ponavljanja. (Svi dijelovi koji se tiču hrane su bili identični, samo se dio oko mokrenja u krevet malo razlikovao.) Ponavljanje nije baš dobra stvar. U nekoj većoj petlji se ne bismo mogli baš najbolje snaći jer ponekad ima stvari koje želimo uraditi između određenih pitanja. U ovakvim situacijama, najbolje je napisati vlastitu metodu. Evo i kako:

def sayMoo

puts 'mooooooo...'

end

Uh...program nije mukao kao krava. Zašto ne? Nismo mu rekli da to uradi. Hajde da pokušamo ponovo:

def sayMoo

puts 'mooooooo...'

end

sayMoo

sayMoo

puts 'coin-coin'

sayMoo

sayMoo

Ispis:

mooooooo...

mooooooo...

coin-coin

mooooooo...

mooooooo...

Ah, to je već puno bolje. (U slučaju da ne govorite francuski, to je u sredini patka. Na francuskom se patke oglašavaju sa „coin-coin“.)

Tako, definisali smo metodu sayMoo. (Imena metoda, kao i imena varijabli, počinju malim slovom. Postoje, ipak, iznimke kao što su + ili ==.) Zar ne trebaju metode uvijek biti povezane s nekim objektima? Naravno da trebaju,...u ovom slučaju (kao što je to i sa –puts- i –gets-) metoda je povezana sa objektom koji predstavlja čitav program. U slijedećem poglavlju ćemo vidjeti kako dodati metode drugim objektima. Ali, prije nego nastavimo, samo ćemo nakratko pogledati šta su...

Parametri metoda

Primjetili ste da neke od metoda (kao -gets-, -.to_s-, -reverse-...) možete jednostavno pozvati pridružujući ih objektu. Međutim, drugim metodama (kao što su –[+]-, -[-]-, -puts-) su potrebni parametri koji govore objektima kako da izvrše određenu metodu. Naprimjer, ne možete napisati samo 5+, zar ne? Govorite petici da doda/sabere, ali ne govorite šta da doda.

Parametri se dodaju metodama na slijedeći način:

def sayMoo numberOfMoos

puts 'mooooooo...'*numberOfMoos

end

sayMoo 3

puts 'oink-oink'

sayMoo # Ovdje bi se trebala pojaviti grješka, jer je parametar prazan.

Ispis:

mooooooo...mooooooo...mooooooo...

oink-oink

#<ArgumentError: wrong number of arguments (0 for 1)>

numberOfMoos varijabla je koja predstavlja parametar naše metode. Reći ću to još jednom, numberofMoos je varijabla koja predstavlja parametar naše metode. Dakle, ako unesem u kod „sayMoo 3“, onda je parametar moje metode 3, a numberOfMoos ukazuje na tricu.

Kao što možete vidjeti, parametar je u ovom slučaju obavezan/neophodan. Kako će vaš jadni računar znati da treba ponavaljati „mooo“ ako mu ne podesite parametar za takvu akciju.

Ako je objekat za Ruby ono što je imenica za jezik, metoda glagol, onda bi parametar bio jednak prilogu (priloška odredba) (u našem primjeru bi nam parametar trebao otprilike reći kako da izvrši „sayMoo“) ili ponekad kao direktnom objektu (kao –puts-, gdje se parametar ispisuje zbog komande koju –puts- daje).

Lokalne varijable

U slijedećem programu, postoje dvije varijable...:

def doubleThis num

numTimes2 = num*2

puts num.to_s+' doubled is '+numTimes2.to_s

end

doubleThis 44

Ispis:

44 doubled is 88

…te varijable su num i numTimes2. Obje su smještene unutar doubleThis metode. Ove dvije (kao i sve varijable koje smo imali priliku vidjeti do sada) nazivaju se lokalne varijable. To znači da one „žive“/vrijede samo unutar određene metode i da izvan nje ne mogu biti primjenjivane...pokušamo li, javit će se grješka:

def doubleThis num

numTimes2 = num*2

puts num.to_s+' doubled is '+numTimes2.to_s

end

doubleThis 44

puts numTimes2.to_s

Ispis:

44 doubled is 88

#<NameError: undefined local variable or method `numTimes2' for #<StringIO:0x82ba7a8>>

Nedefinisana lokalna varijabla,...mi jesmo definisali tu lokalnu varijablu, ali ona nije lokalna tamo gdje smo je pokušali koristiti, nego je lokalna za metodu.

Možda se čini nepovoljnim, ali je to ustvari dobra stvar. To znači da nema pristupa varijablama koje se nalaze unutar metoda, ali i da metode ne mogu pristupiti varijablama,...gdje postoji mogućnost da ih te metode izmjene:

def littlePest var

var = nil

puts 'HAHA! I ruined your variable!'

end

var = 'You can\'t even touch my variable!'

littlePest var

puts var

Ispis:

HAHA! I ruined your variable!

You can't even touch my variable!

U ovom malom programu, postoje dvije varijable imena var: jedna unutar littlePest metode, a druga izvan nje. Kad pozovemo metodu littlePest na izvršenje, nismo ništa drugo uradili nego prebacili string iz jedne varijable u drugu, tako da obje ukazuju na isti string. Nakon toga littlePest ukazuje svoju lokalnu varijablu na nil (ništa), ali to nije promijenilo vanjsku varijablu var.

Povratne vrijednosti

Možda ste primjetili da neke metode daju povratnu vrijednost kad ih pozovemo. Naprimjer, integrisana metoda –gets- vraća string koji smo unijeli putem tastature/tipkovnice, a metoda –[+]- u 5+3 vraća vrijednost 8. Aritmetičke metode za brojeve vraćaju brojeve, a aritmetičke metode za string vraćaju isti.

Važno je da razumijete razliku između metoda koje imaju povratne vrijednosti – daju rezultat - od metoda koje po pozivu ispisuju informacije na ekranu, kao što to radi metoda –puts-. Obratite pažnju,...5+3 vraća vrijednost 8, ne ispisuje informaciju 8.

Šta onda metoda –puts- vraća? Ranije nismo marili za to, ali hajde da pogledamo sada:

returnVal = puts 'This puts returned:'

puts returnVal

Ispis:

This puts returned:

nil

Dakle, prvi je –puts- vratio nil (ništa). Iako nismo testirali i drugi je vratio istu vrijednost; -puts- uvijek vraća nil (ništa) [k.p.: primjetit ćete, ukoliko koristite irb - interactive Ruby- da će poslije -puts- biti naznačena često i linija u kojoj stoji nil]. Na kraju, svaka metoda mora nešto vratiti, pa makar to bila i nil vrijednost (ništa).

Napravite malu pauzu i napišite program koji će saznati šta je povratna vrijednost metode sayMoo.

Iznenađeni? Evo kako to funkcioniše: vrijednost koju metoda vraća jednaka je poslijednjoj liniji te metode. U našem slučaju, to znači –puts 'moooo...'*numberOfMos-, što vraća nil, jer je to uvijek povratna vrijednost metode –puts-. Dakle, ako bismo željeli da naše metode vraćaju string 'yellow submarine', jednostavno bismo je trebali staviti na kraj.

def sayMoo numberOfMoos

puts 'mooooooo...'*numberOfMoos

'yellow submarine'

end

x = sayMoo 2

puts x

Ispis:

mooooooo...mooooooo...

yellow submarine

Da pokušamo onaj psihološki test, ponovo, ali ovaj put ćemo napisati metodu koja će postavljati pitanja za nas. Moraće uzeti pitanje kao parametar, a kao povratnu vrijednost daće vrijednost true ako je odgovor tačan (yes), a false ukoliko je odgovor netačan (no). (Iako smo većinom ignorisali odgovor, ipak je dobra ideja učiniti da naša metoda vraća neku vrijednost. Možemo je takođe lakše iskoristiti za pitanje o mokrenju kreveta.) Skratit ću uvod i pozdrav, da bi bilo lakše čitati:

def ask question

goodAnswer = false

while (not goodAnswer)

puts question

reply = gets.chomp.downcase

if (reply == 'yes' or reply == 'no')

goodAnswer = true

if reply == 'yes'

answer = true

else

answer = false

end

else

puts 'Please answer "yes" or "no".'

end

end

answer # Ovo je naša povratna vrijednost (true or false).

end

puts 'Hello, and thank you for...'

puts

ask 'Do you like eating tacos?' # Ovu povratnu vrijednost zanemarujemo.

ask 'Do you like eating burritos?'

wetsBed = ask 'Do you wet the bed?' # Ovu čuvamo u varijabli.

ask 'Do you like eating chimichangas?'

ask 'Do you like eating sopapillas?'

ask 'Do you like eating tamales?'

puts 'Just a few more questions...'

ask 'Do you like drinking horchata?'

ask 'Do you like eating flautas?'

puts

puts 'DEBRIEFING:'

puts 'Thank you for...'

puts

puts wetsBed

Ispis:

Hello, and thank you for...

Do you like eating tacos?

yes

Do you like eating burritos?

yes

Do you wet the bed?

no way!

Please answer "yes" or "no".

Do you wet the bed?

NO

Do you like eating chimichangas?

yes

Do you like eating sopapillas?

yes

Do you like eating tamales?

yes

Just a few more questions...

Do you like drinking horchata?

yes

Do you like eating flautas?

yes

DEBRIEFING:

Thank you for...

false

Nije loše, ha? Bili smo u mogućnosti dodati još pitanja (a to je sada lahko), a naš program ima znatno manje linija u kodu! Za lijenog programera, to je stvarno veliko poboljšanje.

Još jedan veliki primjer

Mislim da bi još jedan primjer bio od pomoći da još bolje upoznate metode. Nazvat ćemo ga englishNumber. Metoda će uzeti broj od korisnika, naprimjer 22, te ga onda vratiti kao broj izgovoren na engleskom jeziku (u ovom slučaju string 'twenty-two'). Trenutno ćemo uvrstiti samo integer vrijednosti između 0 i 100.

(Napomena: Ova metoda koristi novi trik da se vrati na početak, koristeći rezervisanu riječ return, a predstavlja nam i novi izraz u grananju: elsif. Iz konteksta će se lahko izvući zaključak kako funkcionišu oba ova nova dodatka.)

def englishNumber number

# Želimo samo brojeve između 0 i 100.

if number < 0

return 'Please enter a number zero or greater.'

end

if number > 100

return 'Please enter a number 100 or lesser.'

end

numString = '' # Ovo je varijabla za ispis rezultata.

# "left" je koliko nam još broja ostaje za ispisati.

# "write" je dio koji trenutno ispisujemo.

# "napiši" i "ostalo"... kontaš? :)

left = number

write = left/100 # Koliko stotica ima za ispisati?

left = left - write*100 # Oduzeti stotice.

if write > 0

return 'one hundred'

end

write = left/10 # Koliko desetica ima za ispisati?

left = left - write*10 # Oduzeti desetice.

if write > 0

if write == 1 # Uh-oh...

# Pošto ne možemo napisati "tenty-two" umjesto "twelve",

# moramo napraviti iznimku za ovaj slučaj.

if left == 0

numString = numString + 'ten'

elsif left == 1

numString = numString + 'eleven'

elsif left == 2

numString = numString + 'twelve'

elsif left == 3

numString = numString + 'thirteen'

elsif left == 4

numString = numString + 'fourteen'

elsif left == 5

numString = numString + 'fifteen'

elsif left == 6

numString = numString + 'sixteen'

elsif left == 7

numString = numString + 'seventeen'

elsif left == 8

numString = numString + 'eighteen'

elsif left == 9

numString = numString + 'nineteen'

end

# Pošto smo se pobrinuli za za cifre u jedinicama

# nije nam ostalo ništa za ispisati.

left = 0

elsif write == 2

numString = numString + 'twenty'

elsif write == 3

numString = numString + 'thirty'

elsif write == 4

numString = numString + 'forty'

elsif write == 5

numString = numString + 'fifty'

elsif write == 6

numString = numString + 'sixty'

elsif write == 7

numString = numString + 'seventy'

elsif write == 8

numString = numString + 'eighty'

elsif write == 9

numString = numString + 'ninety'

end

if left > 0

numString = numString + '-'

end

end

write = left # Koliko jedinica ima za ispisati?

left = 0 # Oduzeti jedinice.

if write > 0

if write == 1

numString = numString + 'one'

elsif write == 2

numString = numString + 'two'

elsif write == 3

numString = numString + 'three'

elsif write == 4

numString = numString + 'four'

elsif write == 5

numString = numString + 'five'

elsif write == 6

numString = numString + 'six'

elsif write == 7

numString = numString + 'seven'

elsif write == 8

numString = numString + 'eight'

elsif write == 9

numString = numString + 'nine'

end

end

if numString == ''

# Jedino kad je "number" jednak nuli "numString"

# može biti prazan string – odnosno nula - 'zero'

return 'zero'

end

# Do ovog momenta bi trebalo da smo otkrili naš broj

# pa ostaje još samo da ga ispišemo, odnosno vratimo "numString".

numString

end

puts englishNumber( 0)

puts englishNumber( 9)

puts englishNumber( 10)

puts englishNumber( 11)

puts englishNumber( 17)

puts englishNumber( 32)

puts englishNumber( 88)

puts englishNumber( 99)

puts englishNumber(100)

Ispis:

zero

nine

ten

eleven

seventeen

thirty-two

eighty-eight

ninety-nine

one hundred

[k.p.]: INTERVENCIJA PREVODIOCA

----------------

E pa, u ovom je programu nekoliko stvari koji mi se nikako ne sviđaju, Prvo, previše je ponavljanja. Drugo, ne radi sa brojevima preko 100. Treće, previše je zasebnih slučajeva, previše return. Hajde da, koristeći nizove, pokušamo počistiti sve ove nedostatke:

def englishNumber number

if number < 0 # Ne uključujemo negativne brojeve.

return 'Please enter a number that isn\'t negative.'

end

if number == 0

return 'zero'

end

# Nema više posebnih slučajeva! Nema return!

numString = '' # Ovo je string koji ćemo imati za rezultat.

onesPlace = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']

tensPlace = ['ten', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety']

teenagers = ['eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen']

# "left" je koliko nam još broja ostaje za ispisati.

# "write" je dio koji trenutno ispisujemo.

# "napiši" i "ostalo"... kontaš? :)

left = number

write = left/100 # Koliko stotica ima za ispisati?

left = left - write*100 # Oduzeti stotice.

if write > 0

# Evo sad jedan mali trik :

hundreds = englishNumber write

numString = numString + hundreds + ' hundred'

# To se zove "rekurzija". Šta sam upravo uradio.

# Rekao sam ovoj metodi da pozove sama sebe ali s parametrom

# "write" umjesto "number". Upamtite da je "write" trenutno broj

# stotica koje treba ispisati. Nakon što dodamo stotice promjenjivoj

# "numString", a nakon toga dodajemo ' hundred'. Tako, na primjer

# ako smo pozvali englishNumber sa parametrom 1999 ("number" = 1999)

# u tom momentu bi "write" bio 19 a "left" bi bio 99.

# [k.p.]: devetnaest stotina i ostatak 99.

# Najlijenja stvar koji možemo uraditi sad je da naredimo metodi

# englishNumber da ispiše 'nineteen' i samo mu doda ' hundred', a

# onda ostatak metode ispiše 'ninety-nine'.

if left > 0

# Da ne bismo napisali 'two hundredfifty-one'...

numString = numString + ' '

end

end

write = left/10 # Koliko desetica ima za ispisati?

left = left - write*10 # Oduzeti desetice.

if write > 0

if ((write == 1) and (left > 0))

# Pošto ne možemo napisati "tenty-two" umjesto "twelve",

# moramo napraviti iznimku za ovaj slučaj.

numString = numString + teenagers[left-1]

# Faktor "-1" postavljen je iz razloga što je [3] jednak 'fourteen', a ne 'thirteen'.

# Pošto smo se pobrinuli za za cifre u jedinicama

# nije nam ostalo ništa za ispisati.

left = 0

else

numString = numString + tensPlace[write-1]

# Faktor "-1" stavljamo jer je tensPlace[3] jednak 'forty', a ne 'thirty'.

end

if left > 0

# Da ne bismo pisali 'sixtyfour' (spojeno)...

numString = numString + '-'

end

end

write = left # Koliko jedinica ima za ispisati?

left = 0 # Oduzeti jedinice.

if write > 0

numString = numString + onesPlace[write-1]

# Faktor "-1" postavljamo jer je onesPlace[3] jednak 'four', a ne 'three'.

end

# Sad jednostavno ispišemo/vratimo "numString"...

numString

end

puts englishNumber( 0)

puts englishNumber( 9)

puts englishNumber( 10)

puts englishNumber( 11)

puts englishNumber( 17)

puts englishNumber( 32)

puts englishNumber( 88)

puts englishNumber( 99)

puts englishNumber(100)

puts englishNumber(101)

puts englishNumber(234)

puts englishNumber(3211)

puts englishNumber(999999)

puts englishNumber(1000000000000)

Ispis:

zero

nine

ten

eleven

seventeen

thirty-two

eighty-eight

ninety-nine

one hundred

one hundred one

two hundred thirty-four

thirty-two hundred eleven

ninety-nine hundred ninety-nine hundred ninety-nine

one hundred hundred hundred hundred hundred hundred

Ahhhh.... Tako je već mnogo bolje. Program je malo gušći, pa sam iz tog razloga napisao dosta komentara. Radi čak i sa velikim brojevima...iako ne baš nabolje, ne onako kako sam se nadao. Na primjer, 'one trilion' (milijarda) bi ljepše izgledalo, nego vrijednost koju vidimo na kraju ispisa,...ili 'one milion milion' (iako su sve tri vrijednosti tačne). Ustvari, mogli biste to upravo vi i uraditi...

Par stvarčica za oprobati

    • Proširite program englishNumber. Prvo, ubacite hiljadinke. Na taj način će vam program pisati jedna hiljada umjesto deset stotina ili deset hiljada umjesto jedna stotina stotina.
    • Nakon ovog, napravite još jedno proširenje. Dodajte milionite dijelove, tako da se u ispisu pojavi milion umjesto hiljada hiljaada. Onda idite sve dalje i dalje u proširivanju opsega. Šta mislite, koliko daleko možete ići?
    • Kako bi bilo da napišete program weddingNumber? Trebao bi raditi slično kao i englishNumber, osim što bi trebalo dodati riječ 'and' posvuda, vraćajući vrijednosti kao što je 'nineteen hundred and seventy and two' ili na neki drugi način opisati kako trebaju izgledati pozivnice za vjenčanje. Dao bih vam još primjera, ali ni sam baš najbolje ne razumijem kako to ide. Možda biste trebali kakvog organizatora vjenčanja da vam pomogne?
    • Ninety-nine bottles of beer...“ sjećate li se tog programa? Koristeći princip prisutan u englishNumber, ispišite tekst ove pjesme na pravi način, ovaj put. Kaznite svoj računar: neka počne od 9999 (Mada, nemojte odabrati preveliki broj, jer,...pisanje svih tih flaša na ekranu vašeg računara moglo bi potrajati. Sto hiljada flaša bi stvarno potrajalo; a ako li biste odabrali milion, onda biste kaznili ne samo računar, nego i sebe.)

Čestitam! Od ovog momenta, možete se pohvaliti da ste programeri! Naučili ste sve što je potrebno da se napiše jedan program „od nule“. Ako trenutno imate ideja koje biste mogli iskoristiti za pisanje programa,...učinite to odmah! Pretvorite svoje ideje u funkcionalne programe, pokušajte!

Naravno, pisanje programa „od nule“ može nekad i da potraje. Zašto gubiti vrijeme i pisati kod koji je neko već napisao? Hoćete li da vaš program šalje e-mail? Ili želite pohraniti ili učitati fajlova sa vašeg računara? Kako vam se čini generisanje web stranica za tutorial koje stvarno izvršavaju kod, svaki put kad ih učitate? Ruby ima veliki broj različitih objekata koji nam mogu pomoći da napišemo bolje programe za što manje vremena. Zato...idemo dalje...

----------------------------------------------

© 2003-2009 Chris Pine

http://pine.fm/LearnToProgram/