7  Funkce

Funkce jsou základní stavební kámen v R: všechno, co se v R děje, je volání funkce. Proto se potřebujete naučit, jak přesně funkce používat. Je také velmi užitečné se naučit vytvářet vlastní funkce, protože vám to umožní výrazně zjednodušit a zautomatizovat mnoho výpočtů.

V této kapitole se naučíte

7.1 Funkce a jejich užití

Pojem funkce znáte z matematiky. Volně řečeno, funkce je nějaký předpis, který bere vstupy, nějak je využije a vrátí nějaký výstup. Funkce může např. vzít vektor pozorování, spočítat z nich průměr a ten vrátit jako svůj výstup. Tento význam mají funkce i v R. Technicky přesněji je funkce zapouzdřený kus kódu s jasným rozhraním (interface). Uživatel nemusí vědět nic o tom, co se děje uvnitř funkce – stačí mu vědět, jak funkci zavolat (jaké má jméno a jaké bere vstupní parametry) a jaké vrací hodnoty. Jako příklad si představte funkci mean(), kterou už znáte. Abyste ji mohli použít, nepotřebujete vědět nic o tom, jak je funkce naprogramovaná. Stačí, když znáte její jméno, seznam parametrů, které bere, a rozumíte tomu, jakou hodnotu vrací.

Existuje několik důvodů, proč vytváříme a používáme funkce:

  • Některé řádky kódu spouštíme znovu a znovu. Pokaždé dané řádky kopírovat je hloupé: váš výsledný kód by byl zbytečně dlouhý a nepřehledný. Navíc při kopírování vznikne snadno chyba, a to zejména v situaci, kdy v kódu potřebujete změnit jména proměnných. Rozkopírovaný kód se také špatně udržuje: pokud chcete něco změnit, musíte to změnit na mnoha místech a doufat, že jste na žádné nezapomněli. Při změnách také snadno něco pokazíte. Pokud jste naproti tomu svůj kód uložili do funkce, stačí, když jej změníte na jednom jediném místě.
  • Je dobré oddělit kód a jeho interface. Pak můžeme vlastní kód snadno změnit. Pokud se nezměnil interface, je volání kódu stále stejné, takže nemusíme měnit nic jiného.
  • Je snazší předat uživateli funkci než kód. Uživatel (včetně vašeho budoucího já) nemusí tušit, co se děje uvnitř; stačí mu, když ví, jak funkci spustit.
  • Kód je modulárnější, úspornější a lépe se čte a udržuje. Pamatujte, že váš skript musí být schopen přečíst a porozumět mu nejen počítač, ale také lidé (minimálně vy sami v budoucnosti).

Ukažme si to na příkladu. Řekněme, že máme tři dvojice bodů (\(a_1, b_1, \ldots, a_3, b_3\)) a chceme spočítat jejich Euklidovskou vzdálenost. Srovnejte následují dvě možnosti: kód, který vznikl rozkopírováním:

a1 <- c(0, 0)
b1 <- c(1, 1)
d1 <- sqrt(sum((a1 - b1)^2))
a2 <- c(3, 2)
b2 <- c(2, 3)
d2 <- sqrt(sum((a2 - b2)^2))
a3 <- c(5, 5)
b3 <- c(0, 9)
d3 <- sqrt(sum((a3 - b3)^2))

s kódem pomocí funkce:

dist <- function(a, b) {
    sqrt(sum((a - b)^2))
}

d1 <- dist(a1, b1)
d2 <- dist(a2, b2)
d3 <- dist(a3, b3)

Kód napsaný s pomocí nové funkce je přehlednější a stručnější: čím více vektorů chceme normovat, tím více řádků kódu ušetříme. Je také menší šance, že při kopírování něco pokazíme (např. zapomeneme opravit a2 na a3). Kód napsaný s pomocí funkce se také lépe udržuje. Představte si, že se rozhodneme místo Euklidovské vzdálenosti použít např. vzdálenost typu Manhattan. V prvním případě budete muset najít a opravit všechny řádky, kde se vzdálenost počítá. Ve druhém stačí změnit jen tělo funkce dist():

dist <- function(a, b) {
    sum(abs(a - b))
}

Kód napsaný pomocí funkce navíc šetří operační paměť počítače, protože nezaplevelil pracovní prostředí R čtyřmi mezivýpočty.

Pokud to zobecníme, svůj kód byste měli přepsat jako funkci v jednom z několika následujících případů:

  1. Pokud nějaký kus kódu rozkopírováváte, měli byste z něj raději vytvořit funkci. (V programování obecně platí zásada, že byste se neměli nikdy opakovat, takže každá konkrétní hodnota nebo operace by měla být v kódu definovaná jen na jednom jediném místě, kde je snadné ji konzistentně změnit.)
  2. Pokud potřebujete, abyste mohli nějaké funkci předat svůj kód jako parametr, pak jej také potřebujete zabalit do funkce (touto situací se budeme zabývat v kapitole 9).
  3. Pokud je váš kód velmi dlouhý (nevejde se na obrazovku), pak byste měli zvážit jeho rozdělení na kusy. Zde se nabízí dvě možnosti: (a) rozdělit kód na kusy vizuálně pomocí komentářů a hlaviček (RStudio umožňuje vložit do kódu komentářové hlavičky klávesovou zkratkou Ctrl-Shift-R; zobrazuje je pak vlevo dole v okně editoru a případně v osnově v pravém pruhu vedle editoru, což zapnete ikonkou osnovy); nebo (b) rozdělit svůj kód na funkce. (Stejně tak, pokud vytvoříte funkci, jejíž kód je delší než jedna obrazovka, měli byste zvážit ji rozdělit do více kratších funkcí, protože tím výrazně zvýšíte čitelnost svého kódu.)

Pokud jste na pochybách, zda vytvořit funkci, raději ji vytvořte. Většina začátečníků chybuje spíše tím, že vytváří složitý dlouhý monolitický kód, než že by jej příliš členila do jednotlivých funkcí. (V R je zvykem vytvářet mnoho malých funkcí, které se navzájem volají.)

7.2 Tvorba funkce

V R je funkce objekt jako jakýkoli jiný. To znamená, že vytvořenou funkci jde přiřadit do proměnné, funkci jde předat jako parametr jiné funkci (viz např. kapitola 9), 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).

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, která je dostupná 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.

Všimněte si, že jméno funkce není její součástí. Funkce je v R objekt, který může být k nějakému jménu přiřazen pomocí klasického přiřazovacího operátoru šipka (<-), ale také nemusí. Pokud funkci takto přiřadíme do proměnné, vznikne normální pojmenovaná funkce, kterou můžeme volat jménem této proměnné. Pokud funkci do proměnné neuložíme, vznikne anonymní funkce (nebo lambda funkce).1 Použití anonymních funkcí uvidíte v kapitole 9.

Pojmenovanou funkci tedy vytvoříme tak, že výsledek vrácený funkcí function() uložíme do proměnné. Touto proměnnou bude jméno nové funkce. 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. Tělo funkce je výraz uvedený za koncovou kulatou závorkou za function.

Toto jméno musí samozřejmě splňovat všechny nároky na platné názvy proměnných v R, viz oddíl 2.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()). 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ě. To mimo jiné usnadňuje použití funkce s “trubkami” |> a %>%, viz oddíl 4.6.

Novou funkci tedy vytvoříme takto:

jmeno_funkce <- function(parametry_funkce_oddělené_čárkami)
    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:

jmeno_funkce <- function(parametry_funkce_oddělené_čárkami) {
    výraz 1
    výraz 2
    ...
    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:

vynasob <- function(x, y)
    x * y

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:

vynasob <- function(x, y) {
    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)
    }
    x * y
}
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 6.3. Naši funkci bychom v tom případě mohli přepsat např. do tvaru:

vynasob <- function(x, y) {
    stopifnot(is.vector(x) && is.vector(y) && is.numeric(x) && is.numeric(y) &&
        length(x) == 1 && length(y) == 1)
    x * y
}
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:

vynasob2 <- function(x, y = 2)
    x * y
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.

f <- function(x, y)
    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:

f <- function(x, y = 3 * x + 1)
    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:

a <- 3
b <- 7
f <- function(x, y) {
    a <- 5
    x <- 2 * x
    a + x + y
}
f(b, 3)  # vrací 5 + 2 * 7 + 3 = 22
[1] 22
a  # hodnota a se mimo funkci nezměnila
[1] 3
b  # ani hodnota b se mimo funkci nezměnila
[1] 7

Vždy byste se měli snažit o to, aby každá vaše funkce byla čitelná a srozumitelná nejen stroji, ale i člověku. (Možná ji v budoucnu budete muset mírně změnit. Pokud jí nebudete rozumět, budete ji muset napsat zbrusu znova.) K tomu vám výrazně pomůže odsazení kódu (doporučuji používat odsazení o 4 mezery, ne v R obvyklé 2 mezery) a obecně i dodržování jednotného stylu kódování, viz oddíl 1.8.

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)
vynasob <- function(x, y)
    x * y

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 napsání byste měli každou funkci vyzkoušet s různými vstupy. U platných vstupů byste měli hlídat, že funkce vrací hodnoty, jaké vracet má. U neplatných vstupů by funkce měla co nejdříve “zemřít” a vrátit přitom nějakou smysluplnou chybovou hlášku. (Je velmi nepříjemné, pokud se chybná hodnota dlouho táhne výpočtem, protože pak může být těžké zjistit, kde se co pokazilo. Proto se snažte dodržovat princip “die early”.) Pokud víte něco o testování chyb v softwaru obecně, můžete se podívat na balík testthat, který umožňuje efektivně psát jednotkové testy (unit tests). Dokumentaci balíku najdete v Wickham a Bryan (2023), kap. 13, dosputné i na adrese http://r-pkgs.had.co.nz/tests.html.

7.3 Volání funkce

Funkce se vždy volá se závorkami, i kdyby neměla žádné parametry. Pokud zavoláme funkci bez závorek, vypíše se kód funkce (její tělo):

hello_world <- function()
    print("Ahoj, světe!")
hello_world()
[1] "Ahoj, světe!"
hello_world
function()
    print("Ahoj, světe!")

Parametry mohou být funkci předány třemi způsoby:

  • jménem, např. f(a = 1, b = 2) – v tomto případě nezáleží na pořadí parametrů,
  • pozicí, např. ve funkci f(a, b) znamená volání f(1, 2), že \(a=1\) a \(b=2\), nebo
  • pokud má parametr implicitní hodnotu, je možné jej vynechat – R vezme místo parametru implicitní hodnotu.

Při zadání parametru jménem R umožňuje jméno parametru zkrátit, pokud je zkratka jednoznačná (tomu se říká partial matching). Ve funkci f(number, notalk) je např. možné první parametr zkrátit na num i nu, ale ne na n, protože n není jednoznačné a R by nevědělo, zda n znamená number, nebo notalk. Zkracování parametrů zjednodušuje interaktivní práci; při psaní skriptů se však výrazně nedoporučuje, protože autor funkce by později mohl přidat další parametr a zkrácené jméno už by nemuselo být jednoznačné.

Předávání parametrů těmito třemi způsoby je možné libovolně míchat, tj. volat některé parametry pozicí, jiné jménem a další (s implicitní hodnotou) vynechat. V takovém případě postupuje R takto:

  1. vezme parametry volané plným jménem (exact matching) a přiřadí jim hodnoty,
  2. vezme parametry volané zkráceným jménem (partial matching) a přiřadí jim hodnoty a
  3. vezme parametry pozičně.

Ukážeme si to na příkladu funkce mean(). Tato funkce má podle dokumentace pro číselné vektory použití mean(x, trim = 0, na.rm = FALSE, ...). Následující výrazy jsou tedy ekvivalentní:

mean(x, na.rm = TRUE)
mean(x, trim = 0, na.rm = TRUE)
mean(x = x, trim = 0, na.rm = TRUE)
mean(x, 0, TRUE)

Výraz mean(x, TRUE) už však ekvivalentní není a skončil by chybou.

Pokud nechcete mít v kódu zmatek, doporučuji abyste pozicí volali pouze několik první nejdůležitějších parametrů. Všechny ostatní parametry volejte plným jménem. parametry volané jménem uveďte až za parametry volané pozicí. Ve funkci mean() tedy pozičně předejte jen průměrovaný vektor a ostatní parametry volejte jménem, např.:

mean(x, na.rm = TRUE)

7.4 Speciální parametr ...

Kromě explicitně vyjmenovaných parametrů může funkce v R obsahovat i speciální parametr tři tečky (...). Tento parametr absorbuje libovolný počet parametrů na dané pozici; všechny parametry uvedené za ním musejí být tedy volány plným jménem.

Parametr ... se používá zejména ve dvou situacích: 1) když počet parametrů není dopředu znám a 2) když chceme některé parametry předat z naší funkce další funkci, kterou naše funkce volá. Příkladem funkce, která bere libovolný počet vstupních parametrů, je funkce paste(), která umožňuje spojit libovolný počet řetězců: buď spojuje vektory po prvcích, nebo kolabuje vektor do jednoho řetězce. Počet parametrů není znám dopředu, proto funkce paste používá parametr ...:

paste("Ahoj,", "lidi.", "Jak se máte?")
[1] "Ahoj, lidi. Jak se máte?"
paste
function (..., sep = " ", collapse = NULL, recycle0 = FALSE) 
.Internal(paste(list(...), sep, collapse, recycle0))
<bytecode: 0x560ead96c690>
<environment: namespace:base>

Pokud chcete napsat vlastní funkci s libovolným počtem parametrů, můžete použít následující trik: parametr ... vložíme do seznamu, s jehož prvky pak už pracujeme obvyklým způsobem. Jako příklad použití ... vytvoříme funkci, která vezme libovolný počet atomických vektorů a vrátí součet počtu prvků všech těchto vektorů:

count_all_members <- function(...) {
    param <- list(...)
    sum(purrr::map_int(param, length))
}
count_all_members(1)
[1] 1
count_all_members(1, 1:10, 1:100, 1:1000)
[1] 1111

(Co přesně dělá funkce map_int() z balíku purrr, vysvětlíme v kapitole 9.)

Příkladem funkce, která předává svoje parametry dál, je funkce print(), která dokáže vypsat do konzoly obsah mnoha různých objektů – pro každý objekt volá speciální metodu, tj. funkci přizpůsobenou tomuto objektu. Proto je třeba mít možnost funkci print() předat libovolné parametry, které funkce print() předá dál zvolené metodě:

print
function (x, ...) 
UseMethod("print")
<bytecode: 0x560eb05f52a8>
<environment: namespace:base>

(O objektech a metodách se dozvíte více v kapitole 8.)

7.5 Scoping rules a uzávěry

Když R hledá hodnotu nějaké proměnné, prochází série různých prostředí (environment). Pokud např. zadáte výpis proměnné x v konzoli, začíná R hledat x nejdříve v globální prostředí (tj. základním pracovním prostředí R). Když ji tam nenajde, pokračuje do mateřského prostředí (parental environment) globálního prostředí, což je typicky poslední načtený balík. Pokud ji nenajde ani tam, pokračuje do dalšího mateřského prostředí atd. Pokud ji nikde nenajde, vypíše R chybu.

Situace ve funkcích je zajímavější. R používá tzv. lexical scoping, což znamená, že vyhledávání začíná v prostředí, ve kterém byla funkce definovaná. Pokud je funkce zadefinovaná v globálním prostředí, vše funguje stejně jako v předchozím případě: pokud se nějaká proměnná nenajde v prostředí funkce, bude se dál hledat v globálním prostředí:

a <- 7
nasob_a <- function(x)
    a * x
nasob_a(5)  # neskončí chybou, ale vrátí 35
[1] 35

Náš kód neskončí chybou, protože když R nenašlo proměnnou a v těle funkce ani mezi jejími parametry, začne ji hledat v globálním prostředí, kde ji (v našem případě) najde. Volání funkce nasob_a() by skončilo chybou jen v případě, že by R nenašlo proměnnou a ani v globálním prostředí, ani v žádném balíku načteném do paměti. Použití proměnné, která není definovaná ve funkci, ale v globálním prostředí (jedná se o tzv. globální proměnnou), umožňuje dělat zajímavé triky; zároveň se však takový kód špatně ladí. Snadno se totiž může stát, že jste proměnnou prostě jen zapomněli definovat – pokud daná proměnná žije v globálním prostředí, dostanete divné chování. Pokud však máte v RStudiu zapnutou diagnostiku, RStudio označí proměnné, které nejsou v daném rozsahu (scope) definované žlutou vlnovkou a na začátek řádku přidá výstražný trojúhelníček. (Víc o diagnostice v RStudiu najdete na https://goo.gl/ovWFpE.)

Hodnoty globálních proměnných hledá R dynamicky (dynamic lookup). To znamená, že začne proměnnou hledat v místě, kde byla funkce definovaná, ale použije její aktuální hodnotu, jak ukazuje následující případ:

a <- 7
nasob_a <- function(x)
    a * x
nasob_a(5)  # a je nyní 7
[1] 35
a <- 5
nasob_a(5)  # a je nyní 5
[1] 25

Pokud se ve funkci pokusíte něco přiřadit do globální proměnné, R automaticky vytvoří lokální proměnnou stejného jména, která zamaskuje globální proměnnou. Pomocí operátoru šipka (<-) tedy není možné přiřazovat hodnoty mimo vlastní tělo funkce:

a <- 5
nasob_a <- function(x) {
    a <- 7
    a * x  # hodnota a je ve funkci lokálně 7
}
nasob_a(5)
[1] 35
a  # hodnota a je mimo tělo funkce stále 5
[1] 5

Pokud je funkce g() definovaná uvnitř jiné funkce f(), pak vyhledávání začíná v prostředí vnitřní funkce g(), a pak v prostředí vnější funkce f(). To je možné (mimo jiné) využít k tvorbě tzv. function factories. Ukažme si to na velmi konvenčním příkladu:

n <- 17
make_power <- function(n) {
    g <- function(x)
        x ^ n
    g
}
square <- make_power(2)
cube <- make_power(3)
square(2)  # ve funkci square() je n lokálně rovno 2
[1] 4
cube(2)  # ve funkci cube() je n lokálně rovno 3
[1] 8
n
[1] 17

Funkce make_power() vrací funkci jedné proměnné. Tyto funkce však obsahují kromě formálního parametru x také volný parametr n. Když R hledá jeho hodnotu, začne nejdříve v prostředí, ve kterém byly funkce square() a cube() definovány. Tato prostředí je prostředí funkce make_power() ve chvíli, kdy byla tato funkce spuštěna. Funkce square() byla definována v prostředí, ve kterém bylo n = 2, a toto prostředí si s sebou táhne. Stejně tak funkce cube() si s sebou táhne prostředí, ve kterém je n = 3. (Normálně volací prostředí funkce ve chvíli ukončení jejího běhu zaniká a každá funkce je vždy spuštěna z čistého stavu.)

Funkce square() a cube() jsou tzv. “uzávěry” (closures). Existují situace, kdy je vytváření uzávěr užitečné. Jindy však uzávěry vzniknou, aniž byste si to výslovně přáli. To pak může být zdrojem překvapivých chyb. Pokud nevíte, co děláte, raději nedefinujte žádné funkce v těle jiných funkcí a nepoužívejte příliš globální proměnné.

Typická situace, kde se něco pokazí kvůli nepochopení toho, jak R hledá proměnné, nastává např. v následující situaci: Vytvoříme funkci f(), jejíž hodnota závisí částečně na explicitně zadaném parametru n a částečně na implicitním (globálním) parametru z. Funkci f() pak chceme využít ve funkci g(), která nastaví hodnotu z podle svých potřeb. Jak ale ukazuje následující příklad, nebude to fungovat, protože z použité ve funkci f() se nehledá ve funkci g(), nýbrž v globálním prostředí, kde byla funkce f() definovaná:

z <- 3
f <- function(n) n * z
g <- function(a) {
    # neúspěšná snaha nastavit z
    z <- 7
    # f() hledá z v prostředí, kde byla definovaná, ne kde je spuštěná
    f(a)
}
g(5)  # vrátí 15, ne 35, protože z je 3, ne 7!
[1] 15

Víc se o funkcích a uzávěrách můžete dočíst v 6. a 10. kapitole Wickham (2014), dostupných na http://adv-r.had.co.nz/Functions.html a http://adv-r.had.co.nz/Functional-programming.html.

7.6 Seznam užitečných funkcí

R má implementováno velký objem velmi užitečných funkcí. Kapitola Vocabulary v knize Wickham (2014) uvádí seznam některých velmi užitečných funkcí. Velmi doporučujeme, abyste si tento slovník prošli a naučili se z něj co nejvíce funkcí. Jejich význam a použití najdete v dokumentaci. Celá kapitola je dostupná na http://adv-r.had.co.nz/Vocabulary.html.


  1. R od verze 4.1 nabízí i zkrácenou syntaxi pro tvorbu anonymních funkcí: \(x) {...}, kde výraz ve složených závorkách {...} nahrazuje tělo funkce. I takto vytvořenou funkce je samozřejmě možné uložit do proměnné, a tak ji pojmenovat: f <- \(x) sin(x).↩︎