8.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

Nas kod neskonci chybou, protoze kdyz R nenaslo promennou a v tele funkce ani mezi jejimi parametry, zacne ji hledat v globalnim prostredi, kde ji (v nasem pripade) najde. Volani funkce nasob_a() by skoncilo chybou jen v pripade, ze by R nenaslo promennou a ani v globalnim prostredi, ani v zadnem baliku nactenem do pameti. Pouziti promenne, ktera neni definovana ve funkci, ale v globalnim prostredi (jedna se o tzv. globalni promennou), umoznuje delat zajimave triky; zaroven se vsak takovy kod spatne ladi. Snadno se totiz muze stat, ze jste promennou proste jen zapomneli definovat - pokud dana promenna zije v globalnim prostredi, dostanete divne chovani. Pokud vsak mate v RStudiu zapnutou diagnostiku, RStudio oznaci promenne, ktere nejsou v danem rozsahu (scope) definovane zlutou vlnovkou a na zacatek radku prida vystrazny trojuhelnicek. (Vic 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) {
    z <- 7  # neúspěšná snaha nastavit z
    f(a)  # f() hledá z v prostředí, kde byla definovaná, ne kde je spuštěná
}
g(5)  # vrátí 15, ne 35, protože z je 3, ne 7!
## [1] 15

Vic se o funkcich a uzaverach muzete docist v 6. a 10. kapitole Wickham (2014), dostupnych na http://adv-r.had.co.nz/Functions.html a http://adv-r.had.co.nz/Functional-programming.html.

References

Wickham, Hadley. 2014. Advanced R. 1st ed. Boca Raton, Florida, USA- Chapman; Hall/CRC. http://adv-r.had.co.nz/.