8.2 Tvorba funkce
V R je funkce objekt jako jakykoli jiny. To znamena, ze vytvorenou funkci jde priradit do promenne, funkci jde predat jako parametr jine funkci (viz napr. kapitola 10), funkci můžeme vytvořit i uvnitř jiné funkce (vznikne tzv. nested function) a jedna funkce může vracet jinou funkci jako svou hodnotu (takto vráceným funkcím se říká “uzávěry,” closures; příkladem takové uzávěry jsou funkce e()
, které vrací funkce ecdf()
použitá výše).
Funkce v R nemusejí být čisté funkce, ale mohou mít i vedlejší účinky (side effects). Příkladem takové funkce je např. funkce print()
– místo, aby vracela nějakou hodnotu, vypíše svůj parametr nějakým způsobem do konzoly. Je jednodušší a bezpečnější psát čisté funkce bez vedlejších účinků.
V této kapitole se podíváme pouze na základy psaní funkcí v R. Mnohem podrobnější informace najdete ve Wickham (2014), kap. 6, ktera je dostupna i na http://adv-r.had.co.nz/Functions.html.
Funkci tvoří v R tři části:
- interface funkce, tj. parametry, které funkce bere;
- tělo funkce, tj. kód funkce a
- prostředí (environment) funkce.
Vsimnete si, ze jmeno funkce neni jeji soucasti. Funkce je v R objekt, ktery muze byt k nejakemu jmenu prirazen pomoci klasickeho prirazovaciho operatoru sipka (<-
), ale take nemusi. Pokud funkci takto priradime do promenne, vznikne normalni pojmenovana funkce, kterou muzeme volat jmenem teto promenne. Pokud funkci do promenne neulozime, vznikne anonymni funkce.3 Pouziti anonymnich funkci uvidite v kapitole 10.
Pojmenovanou funkci tedy vytvorime tak, ze vysledek vraceny funkci function()
ulozime do promenne. Touto promennou bude jmeno nove funkce. Toto jmeno musi samozrejme splnovat vsechny naroky na platne nazvy promennych v R, viz oddil 3.2. Navíc byste je měli zvolit tak, aby bylo stručné, jednoznačné a jasně vystihlo, co funkce dělá. Zároveň by nemělo překrýt jméno jiné funkce nebo proměnné. Hadley Wickham doporučuje, že by jméno funkce mělo být sloveso nebo případně podstatné jméno, pokud jasně popisuje dobře definovaný výsledek (jako např. jméno funkce mean()
).
V kulatých závorkách za function
uveďte čárkami oddělený seznam parametrů funkce, tj. vstupních nebo také nezávislých proměnných. Také jména parametrů funkce byste měli zvolit tak, aby se dobře pamatovala a aby jasně vystihla, jakou hodnotu mají obsahovat. Také byste se měli snažit dodržet obvyklé konvence v R, např. pojmenovat parametr pro vyřazení hodnot NA
z výpočtu jménem na.rm
apod. Vstupní parametry mohou v principu obsahovat data nebo různé volby (např. jak zacházet s hodnotami NA
). V R je zvykem dát datové proměnné na první místo a volby uvést až za ně.
Tělo funkce je výraz uvedený za koncovou kulatou závorkou za function
.
Novou funkci tedy vytvoříme takto:
<- function(parametry_funkce_oddělené_čárkami)
jmeno_funkce výraz_který_funkce_vyhodnocuje
Pokud tělo funkce obsahuje víc než jeden výraz, je třeba tyto výrazy sbalit do bloku tím, že je uzavřeme do složených závorek:
<- function(parametry_funkce_oddělené_čárkami) {
jmeno_funkce 1
výraz 2
výraz
...
výraz n }
Funkce nemusí mít žádné parametry ani žádný kód. Pokud má funkce nějaký kód, pak vrací poslední vyhodnocený výraz jako svoji hodnotu.
Pokud je třeba vrátit hodnotu někde jinde než jako poslední výraz v těle funkce, použijeme funkci return()
– ta vrátí hodnotu funkce a zároveň ukončí její běh. To se používá typicky v situaci, kdy pro některé hodnoty vstupů má funkce hned na začátku vrátit nějaký výsledek (např. NA
), místo toho, aby něco složitě počítala.
(Pokud funkce vrací výsledek zabalený do funkce invisible()
, funkce sice vrací hodnotu, ale při použití v konzole výsledek nevypíše.)
Řekněme, že chceme vytvořit funkci, která vezme dvě čísla a vrátí jejich násobek. Tuto funkci vytvoříme takto:
<- function(x, y)
vynasob * y x
Snadno si ověříme, že výsledkem je opravdu funkci a že funguje tak, jak má:
class(vynasob)
## [1] "function"
vynasob(3, 4)
## [1] 12
vynasob(7, 8)
## [1] 56
Pokud funkci plánujete používat hodně a dlouho, měli byste ji zabezpečit proti špatným vstupům. To můžeme udělat dvěma způsoby: buď chceme, aby funkce zhavarovala, nebo aby vypsala varování a vrátila NA
. Druhý případ je jednou ze situací, kdy použijeme funkci return()
. Řekněme, že chceme, aby naše funkce vynasob()
vypsala varování a vrátila NA
v případě, že vstupní proměnné x
a y
nebudou číselné vektory o délce jedna. To můžeme udělat např. takto:
<- function(x, y) {
vynasob if (!is.vector(x) || !is.vector(y) || !is.numeric(x) || !is.numeric(y) ||
length(x) != 1 || length(y) != 1) {
warning("Obě vstupní proměnné musejí být číselný skalár.\n")
return(NA)
}* y
x
}vynasob(2, 3)
## [1] 6
vynasob("a", 3)
## Warning in vynasob("a", 3): Obě vstupní proměnné musejí být číselný skalár.
## [1] NA
Všimněte si, že funkce return()
vrátí hodnotu NA
a ukončí běh funkce, takže pokud je podmínka splněná, na vyhodnocení výrazu x * y
vůbec nedojde.
Pokud chcete funkci ukončit chybovým hlášením, když jsou vstupy špatné, můžete použít funkce stop()
a stopifnot()
, viz oddíl @ref(). Naši funkci bychom v tom případě mohli přepsat např. do tvaru:
<- function(x, y) {
vynasob stopifnot(is.vector(x) && is.vector(y) && is.numeric(x) && is.numeric(y) &&
length(x) == 1 && length(y) == 1)
* y
x
}vynasob(2, 3)
## [1] 6
vynasob("a", 3)
## Error in vynasob("a", 3): is.vector(x) && is.vector(y) && is.numeric(x) && is.numeric(y) && .... is not TRUE
Parametry funkce mohou mít implicitní hodnoty – pokud není hodnota parametru zadána, vezme se jeho implicitní hodnota. Jako implicitní hodnota se volí nejčastější hodnota nebo nejbezpečnější hodnota. Většina funkcí v R, které mají parametr na.rm
, mají tento parametr např. implicitně nastavené na na.rm = FALSE
, protože je bezpečnější implicitně žádné hodnoty nevyřazovat. Implicitní hodnota se nastavuje přímo v seznamu parametrů v kulatých závorkách za voláním function()
.
V následující funkci má proměnná y
přiřazenou implicitní hodnotu:
<- function(x, y = 2)
vynasob2 * y
x vynasob2(3, 4)
## [1] 12
vynasob2(3) # y = 2, implicitni hodnota
## [1] 6
Parametry funkcí jsou vyhodnocovány líně (lazy evaluation). To znamená, že se jejich hodnota vyhodnotí až ve chvíli, kdy jsou opravdu použité. Pokud tedy není parametr ve funkci vůbec použit, R nevyhlásí chybu, když hodnotu parametru nezadáte.
<- function(x, y)
f 3 * x
f(2, 4)
## [1] 6
f(2) # R nevyhlásí chybu, protože y není reálně použito
## [1] 6
Líné vyhodnocování parametrů umožňuje mimo jiné i zadat implicitní hodnoty parametrů, které jsou spočítané z jiných parametrů. Můžeme např. vytvořit funkci f()
, kde implicitní hodnota parametru y
bude záviset na zadané hodnotě parametru x
:
<- function(x, y = 3 * x + 1)
f c(x, y)
f(2) # y je implicitně 3 * 2 + 1
## [1] 2 7
f(2, 3) # y je explicitně zadáno jako 3
## [1] 2 3
Veškeré proměnné definované uvnitř funkce jsou lokální, tj. platí pouze uvnitř funkce a neovlivní nic mimo funkci. Při ukončení běhu funkce zaniknou (leda byste vytvořili uzávěru, viz dále). To platí i pro parametry funkce – pokud se do nich uvnitř funkce pokusíte uložit nějakou hodnotu, R tiše vytvoří lokální proměnnou se stejným jménem, které zastíní vlastní parametr – jeho hodnota nebude ovlivněna. Ukažme si to na příkladu:
<- 3
a <- 7
b <- function(x, y) {
f <- 5
a <- 2 * x
x + x + y
a
}f(b, 3) # vrací 5 + 2 * 7 + 3 = 22
## [1] 22
# hodnota a se mimo funkci nezměnila a
## [1] 3
# ani hodnota b se mimo funkci nezměnila b
## [1] 7
Vzdy byste se meli snazit o to, aby kazda vase funkce byla citelna a srozumitelna nejen stroji, ale i cloveku. (Mozna ji v budoucnu budete muset mirne zmenit. Pokud ji nebudete rozumet, budete ji muset napsat zbrusu znova.) K tomu vam vyrazne pomuze odsazeni kodu (doporucuji pouzivat odsazeni o 4 mezery, ne v R obvykle 2 mezery) a obecne i dodrzovani jednotneho stylu kodovani, viz oddil 2.7.
Pokud hodláte svoje funkce používat dlouhodobě, měli byste je opatřit komentářem dvou typů. První typ komentářů kopíruje nápovědu k funkcím definovaných v balících. Před definici funkce doporučuji si vždy poznačit co funkce dělá, jaké hodnoty mají mít její parametry (co tyto hodnoty znamenají a v jaké datové struktuře a typu mají být uložené) a jakou hodnotu funkce vrací (co výsledek znamená a v jaké datové struktuře a typu je uložen). Neuškodí také, když si napíšete příklad použití funkce. Až se k ní později vrátíte, nebudete muset složitě přemýšlet a zkoušet, jak funkci použít. Komentář pro funkci vynásob()
by mohl vypadat např. takto:
# funkce vynasob() vynásobí dvě čísla
# vstupy:
# - x ... (numeric) první číslo, které má být vynásobeno
# - y ... (numeric) druhé číslo, které má být vynásobeno
# výstup:
# - (numeric) součin čísel x a y
# příklad použití:
# vynasob(2, 3)
<- function(x, y)
vynasob * y x
Druhý typ komentářů patří do vlastního těla funkce. Zde je dobré si poznamenat, jak funkce funguje. Není až tak důležité vysvětlovat co který kus kód dělá a jak to dělá, protože to by mělo být zřejmé z vlastního kódu (pokud to jasné není, měli byste kód přepsat tak, aby to jasné bylo). Spíše je potřeba si poznamenat, proč to dělá. Pokud váš kód také nepracuje obecně s každým možným typem vstupů, je dobré si poznamenat předpoklady, za kterých bude funkce pracovat správně.
Po napsani byste meli kazdou funkci vyzkouset s ruznymi vstupy. U platnych vstupu byste meli hlidat, ze funkce vraci hodnoty, jake vracet ma. U neplatnych vstupu by funkce mela co nejdrive "zemrit" a vratit pritom nejakou smysluplnou chybovou hlasku. (Je velmi neprijemne, pokud se chybna hodnota dlouho tahne vypoctem, protoze pak muze byt tezke zjistit, kde se co pokazilo. Proto se snazte dodrzovat princip "die early.") Pokud vite neco o testovani chyb v softwaru obecne, muzete se podivat na balik testthat, ktery umoznuje efektivne psat jednotkove testy (unit tests). Dokumentaci baliku najdete na viz http://r-pkgs.had.co.nz/tests.html.
References
R od verze 4.1 nabizi i zkracenou syntaxi pro tvorbu anonymnich funkci-
\(x) {...}
, kde vyraz ve slozenych zavorkach{...}
nahrazuje telo funkce. I takto vytvorenou funkce je samozrejme mozne ulozit do promenne, a tak ji pojmenovat-f <- \(x) sin(x)
.↩︎