7 Korisnički definirane funkcije
Programski jezik R, iako objektnog tipa, u svojoj je srži jezik za funkcijsko programiranje. Ova programska paradigma nije nova (datira još iz 50-tih godina prošlog stoljeća), no u posljednje vrijeme dobiva znatno na popularnosti kao svojevrsni komplement objektno-orijentiranom programiranju, za koje bi se moglo reći da je dominantna programska paradigma u zadnjih nekoliko desetljeća.
Kako ne bi preduboko ulazili u značajke funkcijskog programiranja te provodili detaljnu usporednu analizu sa objektno-orijentiranim principima, navesti ćemo samo nekoliko okvirnih značajki i smjernica vezano uz te dvije paradigme. Objektno-orijentirano programiranje u principu program gleda kao sustav imenica gdje komponente realiziramo u obliku objekata koji enkapsuliraju atribute spomenute imenice te metoda koje izvršavaju određene zadatke vezane uz navedenu imenicu. Isto tako, objektno-orijentirani sustavi usredotočavaju se na kontroliranu izmjenu stanja komponenata informacijskog sustava kao rezultat razmjene poruka između njih. Funkcijsko programiranje program gleda kao sustav glagola gdje funkcije, tj zadaci koje želimo izvršavati imaju prioritet naspram komponenata nad kojima se ti zadaci izvršavaju. Funkcijsko programiranje modelira informacijski sustav kroz komponente koje u pravilu ne mijenjaju vlastito stanje, tako da rezultat programa strogo ovisi o ulazima što olakšava testiranje i održavanje. Ukratko, razlika između objektno-orijentiranog programiranja i funkcijskog programiranje se često navodi na sljedeći način: “Kod objektno-orijentiranog programiranja stvaramo podatke koji sadrže funkcije; kod funkcijskog stvaramo funkcije koje sadrže podatke”.
Za učenje R-a ne moramo se previše zamarati sa karakteristikama funkcijskog programiranja niti biti primorani usvojiti potpuno novu programsku paradigmu. No za uspješno učenje R-a svladavanje nekih koncepata funkcijskog programiranja može se pokazati iznimno korisnim budući da će nam omogućiti pisanje čišćeg i učinkovitijeg programskog koda koji će biti u skladu sa načinom na koji je R kao jezik i dizajniran. U R-u vrijedi sljedeće: funkcije su “punokrvni” objekti, možemo ih referencirati varijablom iz birane okoline, slati u funkcije kao argumente, primati kao povratnu vrijednost funkcije ili pohranjivati u podatkovne strukture kao što je npr. lista. Funkcija u R-u je jednostavno objekt kojeg se može “izvršiti” tj. natjerati da obavi neki posao. Veliki broj funkcija - pogotovo onih koje zamjenjuju konstrukt programske petlje - radi na principu funkcijskih jezika gdje posao obavljamo na način da deklarativno navedemo koju funkciju želimo primijeniti na kojoj podatkovnoj strukturi te pustimo da programski jezik sam obavlja niskorazinske poslove kao što je iteriranje po strukturi i pripremanje rezultata. Primjere ovoga ćemo naučiti uskoro, a sada se prvo upoznajmo sa sintaksom definiranja funkcije u R-u.
7.1 Kako definirati funkciju
U općenitom slučaju, definicija nove funkcije izgleda ovako:
Uočimo da kod definicije funkcije koristimo operator <-
. Ovo nije slučajno - definicija funkcije nije ništa drugo nego stvaranje objekta klase function
kojeg onda pridružujemo određenoj varijabli; ime varijable zapravo je “naziv” funkcije.
U R-u ne definiramo tipove ulaznih i izlaznih argumenata. Ulazni argumenti imaju ime i opcionalnu nazivnu vrijednost. Funkcija formalno vraća jednu vrijednost, što nije nužno restrikcija ukoliko želimo vratiti više vrijednosti - jednostavno ih enkapsuliramo u obliku vektora ili liste. Ključna riječ return
je opcionalna - funkcija vraća rezultat zadnjeg izraza u funkciji pa je često dovoljno navesti samo varijablu koja predstavlja povratnu vrijednost kao zadnji red funkcije.
Konačno, ukoliko želimo povećati robusnost funkcije na način da ćemo odbiti izvođenje logike unutar funkcije ako nisu zadovoljeni određeni uvjeti, za to možemo koristiti funkciju stopifnot(<logički izraz>)
. Ova funkcija izračunava zadani logički izraz i prekida funkciju ako navedeni uvjet nije istinit.
Zadatak 7.1 - prva korisnički definirana funkcija
# napišite funkciju `veci` koja prima dva numerička vektora iste duljine
# i vraća vektor koji sadrži veći od dva elementa na istim mjestima
# ukoliko jedan ili oba vektora nisu numerički ili nisu iste duljine,
# funkcija mora izbaciti grešku
# u funkciji nemojte koristiti petlje
# pozovite funkciju `veci` nad kombinacijama vektora
# c(T, F, T) i c(1, 2, 3)
# c(1, 2, 3, 4) i c(5, 6, 7)
# c(1, 2, 3) i c(0, 4, 2)
# (preporuka - drugi dio zadatka isprobati direktno u konzoli!)
# napišite funkciju `veci` koja prima dva numerička vektora iste duljine
# i vraća vektor koji sadrži veći od dva elementa na istim mjestima
# ukoliko jedan ili oba vektora nisu numerički ili nisu iste duljine,
# funkcija mora izbaciti grešku
# u funkciji nemojte koristiti petlje
veci <- function(a, b) {
stopifnot(is.numeric(a) && is.numeric(b) && length(a) == length(b));
ifelse(a > b, a, b)
}
Kod poziva funkcije možemo ali ne moramo navesti imena parametara, a R dozvoljava miješanje imenovanih i neimenovanih parametara (iako to nije nešto što bismo trebali često koristiti u praksi). Kada R bude povezivao poslane vrijednosti sa formalnim parametrima, imenovani parametri imati će prioritet te će se prvi razriješiti, a potom će se redom razrješavati neimenovani parametri.
U ovo se možemo uvjeriti u sljedećem zadatku, u kojem ćemo usput iskoristiti priliku i pokazati rad jedne vrlo često korištene funkcije - paste
. Ova funkcija konkatenira znakovne nizove uz dodavanje razmaka (za spajanje bez razmaka postoji alternativna funkcija paste0
).
Zadatak 7.2 - parametri funkcije
ispisiABC <- function(a, b, c) {
print(paste("A:", a, "B:", b, "C:", c))
}
# razmislite - što ispisuje sljedeći poziv funkcije?
ispisiABC(1, a = 2, 3)
## [1] "A: 2 B: 1 C: 3"
U praksi bismo se trebali držati konvencije da prvo koristimo neimenovane parametre, a potom imenovane. Uobičajeno je da postavljamo samo one imenovane parametre čija nazivna vrijednost nam ne odgovara pri čemu strogi raspored nije bitan (iako će praćenje rasporeda zadanog potpisom funkcije povećati čitljivost našeg koda).
Ako želimo napisati funkciju koja prima proizvoljan broj argumenata, koristimo se elementom ...
, tj. trotočkom. Primjer ovakve funkcije jest gore prikazana ugrađena funkcija paste
koja može primiti proizvoljan broj znakovnih nizova. Ako koristimo trotočku u našoj funkciji, u potpisu ju u pravilu stavljamo na kraj liste argumenata, a unutar same funkcije ju potom jednostavno pretvorimo u listu te potom pristupamo njenim parametrima na način koji nam odgovara.
Zadatak 7.3 - funkcija sa proizvoljnim brojem parametara
ispisiParametre <- function(...) {
parametri <- list(...)
for (p in parametri) print(p)
}
# pozovite gornju funkciju sa proizvoljnim parametrima
## [1] 1 2 3
## [1] 5
## [1] TRUE
## x y
## 1 1 TRUE
## 2 2 FALSE
Za kraj, prisjetimo se priče o S3 objektima i činjenice da R nema formalni sustav stvaranja i korištenja objekata, ali da se preporučuje korištenje zasebne konstruktorske funkcije koja zamjenjuje “ručno” slaganje objekta i deklariranje njegove klase. Sada kada znamo stvoriti vlastitu funkciju možemo pogledati kako bi izgledao mogući konstruktor klase Osoba
.
# konstruktor klase osoba
Osoba <- function(oib, prezime, tezina) {
stopifnot(is.character(oib))
stopifnot(is.character(prezime))
stopifnot(is.numeric(tezina) && tezina > 0)
o <- list(oib = oib, prezime = prezime, tezina = tezina)
class(o) <- "Osoba"
o
}
Pokušajmo uz pomoć prikazanog konstruktora stvoriti novi objekt klase Osoba
.
Zadatak 7.4 - konstruktorska funkcija
# stvorite varijablu `ivo` koja će biti klase `Osoba` a koja će imati sljedeće vrijednosti atributa:
# OIB: 1357135713, prezime: Ivić, tezina: 76
# ispišite varijablu `ivo`
## $oib
## [1] "135135713"
##
## $prezime
## [1] "Ivic"
##
## $tezina
## [1] 76
##
## attr(,"class")
## [1] "Osoba"
Prednost konstruktora je dodatna robustnost u vidu točno definiranih imena atributa ali i mogućnosti ugrađivanja i dodatnih kontrola (npr. OIB
i prezime
moraju biti znakovni nizovi dok tezina
mora biti realni broj i sl.). Ukoliko stvaramo vlastite S3 objekte, preporuka je da za njih definiramo pripadajuće konstruktorske funkcije.
7.1.1 Princip “kopiranja kod izmjene” (copy-on-modify)
Jedno od češćih pitanja koje se postavlja kod učenja novih programskih jezika jest da li funkcije rade na način “poziva preko vrijednosti” (engl. call-by-value) ili “poziva preko reference” (engl. call-by-reference). Razlika se svodi na sposobnost funkcije da mijenja vrijednosti varijabli koje su poslane na mjestu formalnih argumenata funkcije; kod call-by-value principa u funkciju se šalju samo “vrijednosti” parametara, tj. “kopije” originalnih argumenata. S druge strane, kod “call-by-reference” principa funkcija prima “reference” originalnih varijabli, tj. ponaša se kao da su originalne varijable proslijeđene funkciji i sve izmjene nad njima odraziti će se u pozivajućem programu.
Jezik R koristi hibridni princip poznat po nazivom “kopiranje kod izmjene” (engl. copy-on-modify). Kod ovog principa u funkciju se prosljeđuju reference argumenata, što nam omogućuje da prenosimo i “velike” varijable bez straha da će doći do nepotrebnog kopiranja. No ovo vrijedi samo ukoliko funkcija ne mijenja vrijednost dobivenih varijabli - u trenutku kada funkcija pokuša provesti bilo kakvu izmjenu, provodi se kopiranje varijable i funkcija dalje nastavlja rad na kopiji. Zbog ovoga se kaže da R kao takav ne podržava call-by-reference (jedan razlog uvođenja objekata tipa “reference classes” tj. RC objekata u jezik R upravo je uvođenje ovog principa).
Provjerimo gore navedene tvrdnje na konkretnom primjeru.
# pokušaj izmjene varijable iz pozivajuće okoline
f <- function() {
cat("x u funkciji:", x, "\n")
x <- x + 1
cat("x nakon izmjene:", x, "\n")
}
x <- 5
f()
cat("x nakon povratka:", x, "\n")
## x u funkciji: 5
## x nakon izmjene: 6
## x nakon povratka: 5
Funkcija pri izvođenju stvara privremenu vlastitu okolinu unutar koje se pohranjuju “lokalne” varijable. U gornjem primjeru unutar tijela funkcije f
pojavljuje se varijabla x
koja “maskira” vanjsku varijablu x
tako da se sve izmjene više ne odražavaju na vrijednost vanjske varijable. Važno je uočiti da funkcija pristupa vanjskoj varijabli čak i bez njenog slanja u funkciju, budući da referenciranje varijable x
koja ne postoji u lokalnoj okolini funkcije R pretragu nastavio u okolini roditelju što bi u ovom slučaju bila globalna okolina. Pokušaj izmjene ove varijable ipak ne uspijeva - R detektira pokušaj izmjene varijable i stvara lokalnu kopiju istog imena.
Da li ovo znači da funkcija nikada ne može mijenjati varijable iz pozivajuće okoline? Naravno da ne - jedan od načina kako ovo izvesti jest taj da funkciji pošaljemo referencu na okolinu unutar kojeg se nalazi objekt kojeg mijenjamo (ili pustimo funkciju da sama dohvati referencu na globalnu okolinu ukoliko se varijabla tamo nalazi).
Zadatak 7.5 - izmjena varijable globalne okoline
# implementirajte funkciju f tako da dohvati referencu na globalnu okolinu
# i poveća "vanjski" x za 1
x <- 5
# pozovite f(x) i ispisite x
Jednostavniji način rješavanja gornjeg zadatka bio bi korištenjem operatora <<-
. Ovo je tzv. “operator dodjele vanjskom opsegu” (engl. scoping assignment operator), a njegova funkcija jest da izmjeni varijablu zadanog imena koja se nalazi ’negdje" na stazi pretrage. R ide sekvencijalno po stazi pretrage i mijenja prvu pojavu navedene varijable. Ukoliko varijabla tog naziva ne postoji nigdje u stazi pretrage, R će stvoriti novu varijablu u prvoj okolini iznad okoline funkcije.
## [1] 7
Ovaj operator je potencijalno nepredvidiv tako da ćemo veću robusnost ipak postići korištenjem funkcije assign
ili operatora $
uz referencu na okolinu gdje se nalazi varijabla koju želimo mijenjati.
Za kraj spomenimo samo jedno svojstvo funkcija u R-u - tzv. “lijena evaluacija” (engl. lazy evaluation). Ovo jednostavno znači da R neće evaluirati primljeni parametar sve do trenutka kada ga eksplicitno koristimo. Do tog trenutka taj objekt je tzv. “obećanje” (engl. promise) - R “zna” kako evaluirati taj objekt ali to ne radi dok zaista ne treba a do tada imamo samo njegovo obećanje da će to učiniti :) . Na ovaj način povećava se učinkovitost jezika; ako se neki parametar koristi samo u nekoj uvjetnoj grani, onda se u scenarijima kada on nije potreban neće na njega trošiti memorija. No isto tako, moramo biti oprezni jer lijena evaluacija može dovesti do neočekivanih problema ako ne vodimo računa o njenom postojanju.
7.1.2 Funkcija kao objekt
Već smo rekli da R ima kvalitetnu podršku za tzv. “funkcionalno programiranje” što predstavlja programsku paradigmu koja naglasak stavlja na dizajniranje funkcija bez oslanjanja na objekte sa izmjenjivim stanjima. Jedna od karakteristika ovakvih jezika su i tzv. “funkcije prve klase” (engl. first class functions), što zapravo znači da jezik podržava definiciju funkcija na način da su one ravnopravni objekti svim drugim tipovima objekata - mogu se pohraniti u varijablu, koristiti kao ulazni argument druge funkcije ili kao njezina povratna vrijednost, pohranjivati u druge podatkovne strukture i sl. Iako koncepti funkcionalnog programiranja izlaze iz okvira gradiva kojeg želimo prikazati, činjenicu da R tretira funkcije kao sve druge objekte je vrlo važno znati budući da se ova činjenica iznimno često koristi kod programiranja u jeziku R.
Pokažimo ovo na trivijalnom primjeru. Znamo da R nudi funkciju sum
unutar paketa base
koja računa aritmetičku sumu elemenata vektora koje joj proslijedimo. No sum
je zapravo ime varijable koja referencira kod koji implementira dotičnu funkciju. Ukoliko želimo, možemo vrlo lako pridružiti ovu funkciju nekoj drugoj varijabli čime smo joj efektivno “promijenili ime” ili bolje reći dodali alternativni način pozivanja iz potpuno druge okoline.
Ovo je najlakše shvatiti na način da je funkcija jednostavno “varijabla koja se može pozvati”, pri čemu pod “pozivom” smatramo korištenje sintakse koja uključuje referencu na funkciju i ulazne argumente uokvirene u zagrade, a koja će nakon izvršavanja u R okolini vratiti nekakvu vrijednost.
Funkcija može uredno vraćati i neku drugu funkciju.
stvoriteljFje <- function() {
f <- function(x) x + 1
return(f)
}
novaFja <- stvoriteljFje() # dobili smo funkciju "dodavanja jedinice"
novaFja(5)
## [1] 6
Funkcija je jednostavno stvorila novu funkciju i vratila ju pozivajućem programu kao što bi učinila sa bilo kojim drugim objektom. Povratnu vrijednost spremili smo u varijablu koja je sad “pozivljiva” - ako joj dodamo zagrade i parametre ona će se izvršiti na način na koji je definirana unutar funkcije koja ju je stvorila.
Uočite da smo mogli iskoristiti činjenicu da funkcija vraća rezultat zadnjeg izraza i funkciju definirati i kraće:
Ovakve funkcije često se zovu i “tvornicama” ili “generatorima” funkcija, a za razliku od gornjeg primjera u praksi generatoru funkcija često šaljemo i neke parametre koji određuju kako će se vraćena funkcija ponašati.
Pokušajte samostalno napraviti generator funkcija koji vraća funkcije za množenje sa unaprijed postavljenim parametrom.
Zadatak 7.6 - generator funkcija
# stvorite funkciju `tvornicaMnozenja` koja stvara funkcije množenja primljenog broja
# sa nekom predefiniranom konstantom
# uz pomoć gornje funkcije napravite funkciju `puta2` koja udvostručuje primljeni broj
# pozovite funkciju `puta2` s parametrom 3 i ispišite rezultat
# stvorite funkciju `tvornicaMnozenja` koja stvara funkcije množenja primljenog broja
# sa nekom predefiniranom konstantom
tvornicaMnozenja <- function(x) {
function(a) a*x
}
# uz pomoć gornje funkcije napravite funkciju `puta2` koja udvostručuje primljeni broj
puta2 <- tvornicaMnozenja(2)
# pozovite funkciju `puta2` s parametrom 3 i ispišite rezultat
puta2(3)
## [1] 6
Funkcija tvornicaMnozenja
zapravo stvara “porodicu” funkcija koje sve pružaju mogućnost množenja sa odabranim brojem - tj. parametrom kojeg odabire sam programer. Ovakav način upravljanja funkcijama je možda inicijalno zbunjujući, no korištenjem istoga u praksi (što ćemo prikazati već u idućem poglavlju) lako se uočava dodatna fleksibilnost i učinkovitost ovakvog pristupa.
Ako definiramo funkciju, a ne pridružimo ju nekoj varijabli, onda smo stvorili tzv. “anonimnu funkciju”.
Uočimo da je svaka funkcija inicijalno “anonimna”. Ako se vratimo na sintaksu definicije funkcije, vidimo da je ona zapravo kombinacija stvaranja anonimne funkcije i pridjeljivanja iste nekoj varijabli uz operator pridruživanja. Naravno, ostavljanje funkcije anonimnom kao što smo izveli u gornjem primjeru nema previše smisla, isto kao što nema smisla definirati neki vektor ili listu bez pridjeljivanja reference na taj objekt - u tom slučaju stvoreni objekt nije ni na koji način iskoristiv jer nema nijedne poveznice prema njemu te će ga R vrlo brzo obrisati u sklopu rutine “čišćenja smeća”.
Možemo se zapitati - kako onda izgleda scenarij gdje je korištenje anonimne funkcije smisleno i korisno? Eksplicitne anonimne funkcije koristimo kada nam dobro dođe “jednokratna” funkcija, recimo kao argument neke druge funkcije. Ako je funkciju koju želimo poslati kao argument lako definirati u jednom retku, a ne planiramo ju više koristiti u programu, onda nema smisla zasebno ju definirati i dodijeliti joj vlastitu referencu. Primjer ovoga vidjet ćemo u lekciji o apply
porodici funkcija.
Za kraj ovog dijela ponovimo najbitnije stvari - u R-u je funkcija objekt kao i svaki drugi, jedina specifičnost jest da se radi o objektu koji je “izvršiv”, tj. koji uz korištenje sintakse za poziv funkcije obavlja neki posao i vraća neku vrijednost. Čak i anonimnu funkciju možemo bez problema izvršiti (iako samo jednom, budući da nemamo referencu za njezino ponovno zvanje).
## [1] 3
7.1.3 Generičke funkcije
Generičke funkcije već smo spominjali u poglavlju o objektima no svejedno se kratko podsjetimo o čemu se zapravo radi. Programski jezik R svoje objektno orijentirane principe ne temelji na takozvanom “principu razmjene poruka” gdje bi npr. poziv za crtanje grafa mogao izgledati ovako:
već ovako:
U prvom slučaju graf je objekt koji implementira posebnu metodu za crtanje te koju moramo pozvati kako bi dobili traženu sliku grafa. U drugom postoji “vanjska” funkcija koja “zna” nacrtati graf. Ovu funkciju nazivamo generička funkcija.
Općenita svojstva generičke funkcije su sljedeća:
- funkcija ima intuitivnu, jasno određenu svrhu
- očekivani način rada sličan je za više tipova objekata (npr. crtanje će rezultirati slikom)
- svaki tip objekta zahtjeva vlastitu implementaciju ovisno o karakteristikama objekta (npr. način crtanja kruga se razlikuje se od načina crtanja kvadrata)
Način implementacije generičkih funkcija (za S3 objekte!) zapravo je iznimno jednostavan, što je vjerojatno i razlog njihove široke prihvaćenosti i velike popularnosti u R zajednici. Postupak se svodi na tri jednostavna koraka:
- izaberemo naziv generičke funkcije (npr.
ispisi
) i deklariramo da se radi o generičkoj funkciji- alternativno, odaberemo neku od postojećih generičkih funkcija
- stvorimo objekt i deklariramo njegovu klasu (npr.
Osoba
) - implementiramo funkciju naziva
ime_gen_fje.ime_klase
(npr.ispisi.Osoba
)
I to je sve! R ne zahtjeva nikakve dodatne korake, gore navedeno je sasvim dovoljno da R prepoznaje novu generičku funkciju i da ju primjenjuje na sve objekte za čiju klasu je implementirana ta generička funkcija u obliku ime_gen_fje.ime_klase
(ili ime_gen_fje.default
za sve klase za koje ne postoji posebna implementacija).
Pokušajmo u sljedećem zadatku implementirati generičku metodu ispisi
za objekt klase Osoba
.
Zadatak 7.7 - nova generička funkcija
pero <- Osoba(oib = "12345678", prezime = "Peric", tezina = 78)
# stvaramo novu generičku funkciju `ispisi` uz pomoć funkcije `UseMethod`
ispisi <- function(x) UseMethod("ispisi")
# implementirajte funkciju naziva `ispisi.Osoba` koja prima jedan parametar (očekivano klase `Osoba`)
# te na zaslon ispisuje podatke o osobi na sljedeći način:
# OIB: <oib>, Prezime: <prezime>, tezina: <tezina>
# za slaganje ispisa koristite funkciju `paste`
# a za sam ispis funkciju `cat`
# implementirajte funkciju naziva ispisi.default koja prima jedan parametar
# i ispisuje ga na zaslon uz pomoć funkcije `cat`
# ispišite varijablu `pero` uz pomoć generičke funkcije `ispisi`
# ispišite vektor c(1, 2, 3, 4, 5) uz pomoć generičke funkcije `ispisi`
# implementirajte funkciju naziva `ispisi.Osoba` koja prima jedan parametar (očekivano klase `Osoba`)
# te na zaslon ispisuje podatke o osobi na sljedeći način:
# OIB: <oib>, Prezime: <prezime>, tezina: <tezina>
# za slaganje ispisa koristite funkciju `paste`
# a za sam ispis funkciju `cat`
ispisi.Osoba <- function(o) {
rez <- paste("OIB:", o$oib, ", Prezime:", o$prezime, ", tezina:", o$tezina, "\n")
cat(rez)
}
# implementirajte funkciju naziva ispisi.default koja prima jedan parametar
# i ispisuje ga na zaslon uz pomoć funkcije `cat`
ispisi.default <- function(x) cat(x)
# ispišite varijablu `pero` uz pomoć generičke funkcije `ispisi`
ispisi(pero)
# ispišite vektor c(1, 2, 3, 4, 5) uz pomoć generičke funkcije `ispisi`
ispisi(1:5)
## OIB: 12345678 , Prezime: Peric , tezina: 78
## 1 2 3 4 5
Naravno, nismo morali nužno stvoriti vlastitu funkciju ispisi - vjerojatno bi bolji odabir bio korištenje već postojećih generičkih funkcija kao to su print
ili cat
.
Zadatak 7.8 - korištenje postojećih generičkih funkcija
# provjerite da li je print generička funkcija
# (jednostavno ispišite njezin izvorni kod navođenjem samog imena funkcije)
# omogućite ispis klase `Osoba` uz pomoć generičke funkcije `print`
# (možete se poslužiti funkcijom iz prethodnog zadatka)
# ispišite varijablu `Pero` uz pomoć generičke funkcije `print`
# provjerite da li je print generička funkcija
# (jednostavno ispišite njezin izvorni kod navođenjem samog imena funkcije)
print
# omogućite ispis klase `Osoba` uz pomoć generičke funkcije `print`
# (možete se poslužiti funkcijom iz prethodnog zadatka)
print.Osoba <- ispisi.Osoba
# ispišite varijablu `Pero` uz pomoć generičke funkcije `print`
print(pero)
## function (x, ...)
## UseMethod("print")
## <bytecode: 0x000000000f864818>
## <environment: namespace:base>
## OIB: 12345678 , Prezime: Peric , tezina: 78
Za kraj, prikažimo mogućnost R-a da nam izlista sve trenutno poznate implementacije neke generičke metode. Za to se jednostavno koristimo funkcijom methods
kojoj proslijedimo ime dotične metode. Istom funkcijom možemo i provjeriti koje sve implementacije generičkih funkcija postoje za određenu klasu. Za to koristimo parametar class
kojem prosljeđujemo naziv klase za koju tražimo spomenute implementacije.
Zadatak 7.9 - funkcija methods
# prikažite sve do sad poznate implementacije generičke funkcije `summary`
# provjerite koje sve implementacije generičkih funkcija postoje za klasu `factor`
# prikažite sve do sad poznate implementacije generičke funkcije `summary`
#methods(summary) # probati na konzoli
cat("-----------------------\n")
# provjerite koje sve implementacije generičkih funkcija postoje za klasu `factor`
methods(class = "factor")
## -----------------------
## [1] - / [ [[ [[<-
## [6] [<- + all.equal Arith as.character
## [11] as.data.frame as.Date as.duration as.interval as.list
## [16] as.logical as.period as.POSIXlt as.vector as_date
## [21] as_datetime as_factor brief cbind2 coerce
## [26] Compare corresp droplevels fixed format
## [31] gausspr histogram initialize inlearn is.na<-
## [36] is_vector_s3 kqr ksvm length<- levels<-
## [41] Logic lssvm Math Ops plot
## [46] print rbind2 recode relevel relist
## [51] rep rvm scale_type show slotsFromS3
## [56] summary Summary type_sum xtfrm
## see '?methods' for accessing help and source code
Zadaci za vježbu
R ima funkciju
which
koja pretvara logički vektor u numerički sa rednim brojevima elemenata koji suTRUE
(takoc(T, F, F, F, F, T, F, T)
postajec(1, 6, 8)
). Implementirajte vlastitu inačicu ove funkcije.Uzmimo numerički vektor x duljine n. U statistici standardiziranim momentom k-tog reda zovemo vrijednost:
\[\frac{1}{n}\sum_{i=1}^n{(x_i - \bar{x})}^{k+1}\]
Stvorite generator funkcija moment(k)
koji će stvarati funkcije za računanje standardiziranog centralnog momenta k-tog reda. Stvorite funkcije nulti_moment(x)
i prvi_moment(x)
sa vrijednosti parametra k
redom 0
i 1
. Testirajte funkcije na vektoru 1:1000
. Usporedite rezultate koje daje funkcija sd
(standardna devijacija) nad vektorom 1:1000
i korijen rezultata funkcije prvi_moment
nad istim vektorom.
- Implementirajte konstruktor klase
Zaposlenik
koja nasljeđuje objekt klaseOsoba
sa sljedećom konstruktorskom funkcijom i funkcijom ispisa:
Osoba <- function(oib, prezime, tezina) {
o <- list(oib = oib, prezime = prezime, tezina = tezina)
class(o) <- "Osoba"
o
}
print.Osoba <- function(o) {
rez <- paste("OIB:", o$oib, ", Prezime:", o$prezime, ", tezina:", o$tezina, "\n")
cat(rez)
}
Zaposlenik
uz atribute klase Osoba
ima i atribut nadredjeni
koji predstavlja referencu na nadređenog zaposlenika (ukoliko postoji, inače je NA
).
Stvorite dva objekta klase Zaposlenik
(jedan nadređen drugom) i ispišite ih uz pomoć funkcije print
. Potom implementirajte vlastitu inačicu generičke funkcije print
za klasu Zaposlenik
koja ispisuje podatke o zaposleniku i podatke o nadređenom zaposleniku (ako postoji, inače ispisuje poruku da nema nadređenog zaposlenika). Ponovo ispišite oba zaposlenika uz pomoć funkcije print
.
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/