5 Kontrola toka i objekti


5.1 Naredbe kontrole toka

Pod naredbama kontrole toka smatramo uglavnom konstrukte za uvjetno izvođenje naredbi i/ili tzv. “programske petlje” gdje se segment programa kontinuirano izvodi sve do (opcionalnog) ispunjavana određenih uvjeta koji će rezultirati “izlaskom” iz petlje i nastavljanjem programa.

5.1.1 Uvjetno izvođenje naredbi

Uvjetno izvođenje naredbi već smo upoznali. Radi se o konstruktu if (uvjet) { blok } else { blok } pri čemu se vitičaste zagrade mogu izbaciti ako imamo samo jednu uvjetnu naredbu. Ovdje je možda zgodno napomenuti kako izbjeći jednu relativno čestu početničku grešku kod pisanja if naredbe. Pokušajte ju samostalno uočiti i ispraviti u sljedećem primjeru.


Zadatak 5.1 - naredba if


Greška se javlja zbog toga što je R interpreterski jezik koji se u pravilu izvršava redak po redak, osim u slučajevima kada smo R-u proslijedili “nedovršenu” naredbu pri čemu će on čekati na “ostatak” prije no što krene sa izvršavanjem. U prethodnom primjeru druga naredba if je zapravo završena u prvom retku, tako da se R “iznenadi” kada idući redak počinje sa else. Kako bi spriječili ovaj scenarij, dovoljno je na nekim način objasniti R-u da naredba još nije dovršena, što je najlakše izvesti otvaranjem bloka u prvom retku i zatvaranjem u retku sa else.

Čitateljima koji programiraju u jezicima C ili Java biti će poznat pojam tzv. “ternarnog operatora” koji zapravo predstavlja kompaktnu verziju if-else bloka:

Ulogu ovog operatora u R-u obavlja funkcija ifelse.


Zadatak 5.2 - funkcija ifelse

## [1] 2 2 2 2

Uočite da je funkcija ifelse (očekivano) vektorizirana, zbog čega je posebno pogodna za stvaranje novih stupaca podatkovnih okvira koji su izvedeni iz određenih uvjeta vezanih uz postojeće stupce.


5.1.2 Programske petlje

U programskom jeziku R imamo tri tipa petlji:

  • repeat - beskonačna petlja
  • while - petlja s provjerom uvjeta na početku
  • for - iteratorska petlja (“petlja s poznatim brojem ponavljanja”)

5.1.3 Petlja repeat

Petlja repeat je najjednostavnija petlja. Ona ima sljedeću sintaksu:

repeat {blok}

Ovdje se radi o “beskonačnoj” petlji gdje se nakon završetka bloka on ponovo izvršava i tako unedogled. Jedini način izlaska iz ovakve petlje jest korištenje naredbe break. Pored ove naredbe imamo i naredbu next koja će preskočiti ostatak bloka, ali neće izaći iz petlje već će nastaviti izvršavati blok.

Pogledajmo kako radi ova petlja u sljedećoj vježbi.

Zadatak 5.3 - Petlja repeat

## [1] 3
## [1] 5
## [1] 7
## [1] 9
## [1] 11

Često unaprijed znamo uvjet izlaska iz petlje te bismo ga htjeli staviti na jasno vidljivo mjesto tako da nije “skriven” u tijelu petlje. Za to nam pomaže tzv. while petlja.

5.1.4 Petlja while

Petlja while predstavlja “najčišći” oblik programske petlje čija sintaksa doslovno glasi “dok je uvjet ispunjen, ponavljaj navedeni kod”:

while (uvjet) {blok}

Zadatak 5.4 - Petlja while

## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7

Kod ove petlje moramo paziti da se u određenoj iteraciji moraju stvoriti uvjeti za izlaz, inače ona također postaje “beskonačna” petlja. Usprkos tome što imamo jasno definiran način izlaska iz petlje, mi i u ovoj petlji možemo slobodno koristiti ključne riječi next i break, koje imaju istu funkciju kao i kod petlje repeat.

5.1.5 Petlja for

Petlja for ili “iteratorska petlja” služi za lako “šetanje” po nekoj programskoj strukturi (najčešće vektoru), uzimajući element po element i nešto radeći s njim. Ona koristi ključnu riječ for, ime nove (iteratorske) varijable, ključnu riječ in te vektor čije vrijednosti se uzimaju jedna po jedna i koriste unutar petlje (uočite da navedeni in nije isto što i operator %in% koji provjerava da li se neki element nalazi u nekom skupu!). Sintaksa ove petlje je sljedeća:

for (i in v) {radi nešto sa i}

Uočimo da ovdje varijabla i nije “brojač” - u svakoj iteraciji petlje ona postaje vrijednost elementa do kojeg smo došli. Ukoliko baš želimo iterirati po indeksima, a ne po samim elementima, onda možemo koristiti konstrukt for (i in 1:length(a)). Treća varijanta jest iteriranje po imenima (ako smo definirali imena elemenata vektora) - for (i in names(a).

Zadatak 5.5 - Petlja for

Uočite da je drugi način bolji ako želite mijenjati elemente vektora ili imati informaciju na kojem mjestu unutar originalnog vektora se trenutno nalazite. ***

Sad kad smo naučili sintaksu petlji važno je naglasiti jednu činjenicu - u programskom jeziku R u pravilu se ne preporučuje korištenje programskih petlji . Iako ovo inicijalno možda djeluje neočekivano i pomalo šokantno, razlog je jednostavan - R je jezik dizajniran upravo da radi po principu “sve odjednom”. Već smo vidjeli da principu vektoriziranosti i recikliranja učinkovito obavljaju poslove koji bi u drugim programskim jezicima zahtijevali petlju, a u poglavljima koje slijede vidjet ćemo da R nudi i mnoge druge konstrukte koji izbjegavaju eksplicitno ponavljanje koda uz uvjet nauštrb deklarativne sintakse koja to obavlja automatski.

Na primjer, sljedeći primjer je sintaksno potpuno ispravan:

ali vjerojatno radi sporije i puno je nečitljiviji od:

Sve navedeno naravno ne znači da petlje u R-u ne smijemo koristiti, samo da bi njihovo korištenje trebalo biti popraćeno dodatnim razmatranjem da li je na tom mjestu petlja zaista potrebna te da li postoji alternativna sintaksa koji isti posao obavlja deklarativno (i potencijalno brže, budući da su mnoge rutine R-a implementirane u jeziku C). Rano prihvaćanje “R-ovskog” načina razmišljanja rezultirati će dugoročnim benefitom koji će se očitovati kompaktnijim, čišćim i često učinkovitijim programskim kodom.


5.2 Objektni modeli u jeziku R

Kako bismo objasnili što su zapravo generičke funkcije, moramo se vratiti na priču o programskim paradigmama i činjenici da je R dizajniran kao objektno orijentirani jezik, zajedno sa mehanizmima koje objektno orijentirana paradigma nalaže - enkapsulacija (združivanje različitih varijabli u zajedničku cjelinu), polimorfizam (korištenje iste funkcije nad različitim objektima rezultira različitim operacijama ovisno o prirodi objekta) i nasljeđivanje (izvođenje novih objekata iz postojećih na način da ih proširujemo dodatnim elementima).

R je svoj inicijalni način modeliranja objekata preuzeo iz jezika S te su stoga takvi objekti poznati kao “S3 objekti” (prema inačici jezika S iz koje su originalno preuzeti). Ovaj način, kojeg ćemo upoznati u nastavku, zapravo je vrlo nekonvencionalan i jednostavan no kao takav i prilično podoban za korištenje R-a kao primarno domenski orijentiranog jezika. Uplivom sve većeg broja programera u R zajednicu rastao je i pritisak da se u R uvede podrška za objekte koja će biti sličnija načinu kako njima upravljaju drugi programski jezici, a koja bi povećala robustnost kod dizajna i upravljanja objektima.

Sve je to dovelo do toga da danas formalno imamo čak četiri tipa objekata u programskom jeziku R:

  • osnovni objekti (base classes) - osnovni, “bazični” elementi jezika R (funkcije, vektori, podatkovni okviri)
  • S3 objekti - princip dizajna objekata preuzet iz jezika S (inačica 3)
  • S4 objekti - formalniji i rigorozniji način stvaranja objekata koji se približava standardnim objektno-orijentiranim mehanizmima iz drugih jezika
  • RC objekti (reference classes) - najnoviji način stvaranja objekata (uveden u inačici R 2.12) koji u potpunosti replicira “klasične” objektno-orijentirane principe utemeljene na razmjeni poruka

Postojanje tri različita modela definiranja objekata (možemo zanemariti osnovni budući da njega formalno ne možemo “proširivati” novim objektima) može djelovati demotivirajuće - da li je potrebeno naučiti sva tri modela? Kako ih razlikovati? Koji odabrati? No usprkos činjenici da se priča o objektnoj prirodi jezika R tijekom njegova razvoja (nepotrebno) zakomplicirala, dobra vijest je ta da je za većinu potreba sasvim dovoljno naučiti kako radi S3 model, koji je ujedno i najjednostavniji. Veliki broj popularnih R paketa koristi isključivo S3 klase i moguće je raditi dugo vremena u jeziku R bez potrebe za učenjem S4 ili RC modela. Zbog ove činjenice ćemo u nastavku se usredotočiti isključivo na S3 klase (čitatelji koji žele više informacija o ostalim modelima mogu pogledati vrlo dobru knjigu “Advanced R”, autora Hadley-a Wickhama, koja se između ostalog detaljnije bavi objektnim modelima u R-u i njihovom usporedbom).


5.2.1 Pregled objektnog modela S3

Kao što je već rečeno, S3 objekti zapravo su preneseni iz programskog jezika S i predstavljaju relativno primitivno poimanje koncepta “objekta”, barem što se tiče očekivanja glede standardnih metoda stvaranja objekata i pripadajućih metoda. S3 objekt je zapravo obična lista kojoj smo definirali class atribut.

I to je to! Uočite da nemamo formalno definiranog “predloška” klase kojeg onda instanciramo u objekt kao što je ustaljena praksa u drugim programskim jezicima. Kod S3 objekata jednostavno stvaramo listu i onda deklariramo da je ta lista objekt određene klase, iako je struktura te klase zapravo samo implicirana izgledom objekta (i ne mora uopće odgovarati strukturi nekog drugog objekta koji se deklarirao da pripada istoj klasi).

Naravno, ovako ležeran način konstrukcije objekata ipak nije preporučljiv te se zbog toga preporučuje da klase ne deklariramo “ručno” već da to radimo uz pomoć posebne konstruktorske funkcije čiji će parametri zapravo definirati izgled objekta (ovo ćemo naučiti u lekciji o stvaranju vlastitih funkcija)

Što je s nasljeđivanjem, gdje klasa-dijete nasljeđuje tj. proširuje svojstva klase-roditelja?

R omogućuje nasljeđivanje, ali također na vrlo neformalan i relativno trivijalan način. Umjesto da navedemo samo jedan “naziv” klase uz pomoć atributa class, mi stvorimo znakovni vektor gdje će prvi element biti naziv klase, a ostali elementi će biti klase roditelji, poredani prema “važnosti”. Na primjer, ako smo stvorili novi objekt mate klase Zaposlenik nad kojim bi htjeli koristiti iste implementacije određenih generičkih metoda razvijenih za potrebe objekata klase Osoba, onda je dovoljno izvesti sljedeće:

Primijetimo da sav posao oko nasljeđivanja atributa moramo obaviti “ručno”, tj. moramo se sami pobrinuti da mate ima atribute klase Osoba koje će generička funkcija koju pozivamo koristiti.

5.2.2 Generičke funkcije

Gledajući gore definirani način dizajna objekta opravdano je postaviti i dodatno pitanje - a gdje su metode? Kao što znamo, standardni objektno-orijentirani principi pretpostavljaju enkapsulaciju atributa ali i metoda u okvir objekta. Upravo tu leži osnovna razlika između S3 objekta i “standardnih” objekata iz drugih programskih jezika - kod S3 objekata metode se definiraju izvan objekta u obliku tzv. generičkih funkcija.

Zašto je tome tako? Ideja jest sljedeća - u radu sa objektima korisnik (programer, analitičar) često poziva iste funkcije (npr. “ispis”, “crtanje”, “sažeti opis”) nad objektima različitog tipa. Funkcija istog imena ali različite implementacije ovisno o objektu nad kojim radi zove se generička funkcija. Tako recimo funkcija print uvijek rezultira nekakvim ispisom, ali kako će ispis izgledati zapravo ovisi o objektu kojeg ispisujemo.

Ovaj način dizajna objekata može djelovati iznimno nekonvencionalno, no činjenica jest da on pozive funkcija čini puno intuitivnijim, pogotovo korisnicima koji nemaju veliko iskustvo sa programiranjem. Konkretno, usporedimo naredbu:

s naredbom:

Čitajući prvu naredbu auto doživljavamo kao “objekt” (u smislu službe riječi u rečenicu), tj. nešto radimo “nad” tim objektom. Druga naredba auto postavlja kao subjekt, što je uobičajena praksa u objektno-orijentiranim jezicima ali nije u skladu sa općenitim poimanjem obavljanja radnji nad nekim objektima.

U radu s programskim jezikom R često radimo “slične” poslove nad različitim objektima - ispisujemo njihov sadržaj, crtamo ih na grafu, tražimo neke sažete detalje o njima i sl. Upravo zbog toga, a i činjenice da u R-u često radimo interaktivno, R je dizajniran na način da razmišljamo što želimo učiniti umjesto da se pitamo gdje se nalazi funkcija koju želimo pozvati. Ako želimo ispisati neki objekt, logično je da ga samo proslijedimo funkciji print, ako ga želimo nacrtati funkciji plot, a ako želimo sažetak funkciji summary.

Kako pojedina funkcija “zna” što učinit sa objektom? Odgovor je jednostavan - generička funkcija je samo “sučelje” prema “pravoj” funkciji koju pozivamo, a logika kako pronaći pravu funkciju je vrlo trivijalna - ako je ime generičke funkcije genFun a naziv klase objekta koju joj prosljeđujemo nazKlase, funkcija koja se zapravo poziva jest genFun.nazKlase. Ako takve funkcije nema, poziva se funkcija genFun.default.

U ovo se lako možemo uvjeriti samostalno u sklopu sljedeće vježbe.

Zadatak 5.6 - Generičke funkcije

## standardGeneric for "summary" defined from package "base"
## 
## function (object, ...) 
## standardGeneric("summary")
## <environment: 0x00000000120d07b8>
## Methods may be defined for arguments: object
## Use  showMethods("summary")  for currently available ones.
## function (object, maxsum = 100L, ...) 
## {
##     nas <- is.na(object)
##     ll <- levels(object)
##     if (ana <- any(nas)) 
##         maxsum <- maxsum - 1L
##     tbl <- table(object)
##     tt <- c(tbl)
##     names(tt) <- dimnames(tbl)[[1L]]
##     if (length(ll) > maxsum) {
##         drop <- maxsum:length(ll)
##         o <- sort.list(tt, decreasing = TRUE)
##         tt <- c(tt[o[-drop]], `(Other)` = sum(tt[o[drop]]))
##     }
##     if (ana) 
##         c(tt, `NA's` = sum(nas))
##     else tt
## }
## <bytecode: 0x0000000026a37988>
## <environment: namespace:base>

Razumijevanjem principa rada generičkih funkcija upotpunili smo sliku o S3 objektima. Najvažnija stvar koju moramo usvojiti jest da kod ovog modela funkcije nisu dio samog objekta, već se definiraju zasebno, a poveznica između objekta i “njegove” metode jest samo u nazivu funkcije pomoću kojeg će R “povezati” generičku funkciju i taj objekt. Iako je ovaj princip primitivan i podložan greškama u rukama nepažljivog programera, on je neumitno jednostavan za uporabu i vrlo učinkovit. Konačno, uočimo da ovaj pristup nije u potpunosti svojstven isključivo jeziku R - slične principe nalazimo i u drugim programskim jezicima (npr. Python povezuje print funkciju i objekt preko posebne metode objekta __str__). R je samo specifičan po tome što taj princip koristi otvoreno i gotovo isključivo.

Objekte i generičke funkcije ćemo ponovo posjetiti kada naučimo stvarati vlastite funkcije, što će nam omogućiti da stvorimo kako konstruktore naših objekata, tako i njihove generičke funkcije.

5.2.3 Zaključci o S3 objektima

Ukratko, zaključci o S3 objektima mogu biti sljedeći:

  • S3 objekti funkcioniraju na jednostavan, neformalan način - to su jednostavno liste sa postavljenom proizvoljnom vrijednosti class atributa
  • puno toga ostavljeno je na odgovornosti programera
  • metode S3 objekata ne enakpsuliraju se unutar objekata, već se dizajniraju “izvan” objekata u obliku generičkih funkcija
  • S3 objekti jednostavni su za uporabu ako su i objektni modeli koje dizajniramo nisu jednostavni, ali nisu pogodni za kompleksnije objektne modele zbog teškog održavanja modela i velike mogućnosti pogrešaka

Zadaci za vježbu

  1. Stvorite podatkovni okvir mjesto uz pomoć sljedeće naredbe:

Dodajte ovom okviru stupac prirezOpis koji će biti ordinalna faktorska varijabla sa razinama "mali", "srednji" i "visok" ovisno o tome da li je postotak prireza strogo manji od 12, između 12 i 15 ili strogo veći od 15. Koristite se naredbom ifelse.

  1. Zamijenite petlje u sljedećem bloku ekvivalentnim vektoriziranim operacijama (za drugu petlju proučite dokumentaciju funkcije sum).
  1. Stvorite objekt klase Kvadar sa atributima visina, sirina i dubina jednakim 10, 20 i 30.

Creative Commons License
Programirajmo u R-u by Damir Pintar is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
Based on a work at https://ratnip.github.io/FER_OPJR/