Co budeme dělat

  • proč psát funkce
  • jak vytvořit funkci
  • jak volat funkci
  • (lexical scoping)
  • základní řídící struktury

Co je funkce

Funkce je 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 použít:

  • jaké má jméno,
  • jaké bere vstupní parametry
  • jaké vrací hodnoty

Proč vytváříme a používáme funkce

  • některý kód spouštíme znovu a znovu; pokaždé kód kopírovat je hloupé
    • kód je dlouhý a nepřehledný
    • při kopírování vznikne snadno chyba
    • kód se těžko mění (nutno měnit na víc místech naráz)
  • je dobré oddělit kód a jeho interface
    • jde měnit "vnitřek" a ne použití
  • je snazší předat uživateli funkci než kód
    • uživatel (včetně budoucího já) nemusí tušit, co je uvnitř
  • kód je modulárnější, úspornější a lépe se čte a udržuje

Funkce v R (1)

V R je funkce objekt jako jakýkoli jiný. To znamená, že

  • vytvořenou funkci jde vložit do proměnné
  • funkci jde předat jako parametr jiné funkci
  • funkci můžeme vytvořit i uvnitř jiné funkce (nested function)
  • jedna funkce může vracet jinou funkci jako svou hodnotu

Funkce v R (2)

Funkci tvoří tři části:

  • interface funkce, tj. argumenty, které funkce bere
  • tělo funkce, tj. kód funkce
  • prostředí (environment) funkce, které zahrnuje proměnné funkce

Funkce v R (3)

Funkce v R mohou mít vedlejší účinky (side effects).

Příkladem takové funkce je funkce print() – místo, aby vracela nějakou hodnotu, vypíše svůj argument nějakým způsobem do konzoly.

Je jednodušší a bezpečnější psát čisté funkce bez vedlejších účinků.

Tvorba funkce

Funkci vytvoříme tak, že výsledek vrácený funkcí function() uložíme do proměnné:

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 pomocí složených závorek:

jmeno_funkce <- function(parametry_funkce_oddělené_čárkami) {
    výraz_1
    výraz_2
    ...
    výraz_n
}

Funkce vrací poslední vyhodnocený výraz jako svoji hodnotu.

Příklad

nasobek <- function(x, y)
    x * y
class(nasobek)
## [1] "function"
nasobek(3, 4)
## [1] 12
nasobek(7, 8)
## [1] 56

Hodnoty vrácené funkcí

Funkce vrací jako svou hodnotu

  • poslední vyhodnocený výraz
  • obsah funkce return() – při tom skončí
# totéž, co předchozí funkce
nasobek <- function(x, y) {
    n <- x * y
    return(n)
}

Funkce return() se obvykle použije, pokud má funkce skončit svůj běh jinde než na svém posledním řádků.

K procvičení

Napište funkci d(), která vezme vektor a vrátí jeho diferenci.

Hint: použijte indexování.

R má samozřejmě funkci diff(). Na několika vektorech otestujte, že d() dává stejné výsledky jako diff().

Volání funkce s a bez závorek

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
## function()
##     print("Ahoj, světe!")
hello_world()
## [1] "Ahoj, světe!"

Implicitní hodnoty parametrů

Argumenty funkce mohou mít implicitní hodnoty – pokud není hodnota argumentu zadána, vezme se implicitní hodnota:

soucin2 <- function(x, y = 2)
    x * y
soucin2(3, 4)
## [1] 12
soucin2(3)  # y = 2, implicitni hodnota
## [1] 6

Volání funkce (1)

Funkce se volá svým jménem se závorkami.

Argumenty 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\)
  • pokud má parametr implicitní hodnotu, je možné jej vynechat – R vezme místo parametru implicitní hodnotu

Volání funkce: partial matching

Při zadání parametru jménem R umožňuje jméno parametru zkrátit, pokud je zkratka jednoznačná.

Ve funkci f(number, notalk) je možné první parametr zkrátit na num i nu, ovšem ne na n, protože n není jednoznačné.

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

Volání funkce (2)

Předávání parametrů jménem, pozicí a implicitní hodnotou lze libovolně míchat.

R postupuje 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
  3. vezme parametry pozičně

Pokud nechcete mít v kódu zmatek, doporučuji následující:

  • první parametry funkce volat pozicí
  • jménem volat až parametry za nimi

Volání funkce: příklad

Identická volání funkce mean(x, trim = 0, na.rm = FALSE, ...):

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

Ale už ne:

mean(x, TRUE)

Vypsalo by chybu:
Error in mean.default(x, TRUE) : 'trim' must be numeric of length one

Speciální argument ... (1)

Speciální parametr ... libovolný počet parametrů na dané pozici; všechny parametry uvedené za ním musejí být volány plným jménem.

Parametr ... se používá zejména ve dvou situacích

  • když počet parametrů není dopředu znám
  • když chceme některé parametry předat z naší funkce další funkci, kterou naše funkce volá

Speciální argument ... (2)

Funkce paste() spojuje libovolný počet řetězců.

Počet parametrů není znám dopředu, proto funkce paste používá parametr ...:

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

Speciální argument ... (3)

Funkce print() dokáže vypsat do konzoly obsah mnoha různých objektů – pro každý objekt volá speciální 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:

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

Proměnné definované ve funkci

Proměnné definované uvnitř funkce jsou lokální; po ukončení běhu funkce zaniknou. (To platí i pro parametry funkce.)

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

Scoping rules (1)

Když R hledá hodnotu nějaké proměnné, prochází série různých prostředí (environment).

Pokud použijete proměnnou x v konzoli, začíná R hledat x

  • nejdříve v globální prostředí
  • 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

Scoping rules (2)

R používá lexical scoping, což znamená, že vyhledávání začíná v prostředí funkce, a pak v prostředí, ve kterém byla funkce definovaná.

Pokud je funkce definovaná v globálním prostředí, vše funguje normálně.

y <- 5
f <- function(x) {
    x * y  # x se našlo v prostředí funkce, y v globální prostředí
}
f(4)
## [1] 20

Pozor: to, že R hledá proměnné i mimo funkci může vést k těžko hledatelným chybám, když zapomenete proměnnou definovat ve funkci.

Scoping rules (3)

Pokud však funkce definovaná uvnitř jiné funkce, pak vyhledávání začíná v prostředí vnější funkce. To slouží k tvorbě tzv. function factories a uzávěr (closures).

n <- 17
make.power <- function(n) {
    g <- function(x)
        x ^ n
    g
}
square <- make.power(2)
cube <- make.power(3)
c(s = square(2), c = cube(2), n = n)
##  s  c  n 
##  4  8 17

Přiřazení ve funkci

Přiřazení ve funkci přiřazuje hodnotu vždy do lokální proměnné.

Pokud se pokud se přiřazuje do proměnné mimo funkci nebo do parametru funkce, R tiše vytvoří lokální proměnnou se stejným jménem, která původní proměnnou zastíní:

x <- 5
y <- 5
f <- function(z) {
    x <- 2
    z <- 3
    x * z
}
c(x = x, y = y, f = f(7))
## x y f 
## 5 5 6

Řídící struktury a spol.

Zatím jsme předpokládali, že kód skriptu běží řádek po řádku.

Někdy je však potřeba běh kódu různě modifikovat:

  • některé řádky provést pouze, když je splněná určitá podmínka
  • některé řádky provádět opakovaně
  • zastavit běh skriptu s chybovým hlášením

Větvení kódu (1)

K provedení kódu, pouze pokud je splněná nějaká podmínka, slouží if:

x <- 1
if (x == 1)
    print("O.K.: x je jedna!")
## [1] "O.K.: x je jedna!"

Syntaxe: v závorce je logický výraz, na druhém řádku je kód, který se provede pouze v případě, že logický výraz má hodnotu TRUE.

Větvení kódu (2)

Pokud se má provést více než jeden řádek kódu, je třeba jej seskupit pomocí složených závorek:

if (x == 1) {
    a <- 5
    print("O.K.: x je jedna!")
}
## [1] "O.K.: x je jedna!"
a
## [1] 5

Větvení kódu (3)

Pokud se má nějaký kód provést při platnosti podmínky a jiný, pokud podmínka neplatí:

if (x == 1) {
    print("O.K.: x je jedna!")
} else {
    print("O.K.: x není jedna!")
}
## [1] "O.K.: x je jedna!"

Pozor: else musí být na stejném řádku, jako končící složená závorka nebo kód, který se provádí při splnění podmínky.

Větvení kódu (4)

Pokud se podmínka skládá z více logických výrazů, používají se "zkratující" logické operátory && a ||:

x <- 5
y <- 7
if ((x == 1) || y == 7)
    print("Podmínka splněna, rytíři Brtníku!")
## [1] "Podmínka splněna, rytíři Brtníku!"

K procvičení

Napište funkci, která aabs(x), která vrátí absolutní hodnotu čísla (skaláru) x.

(R má samozřejmě funkci abs().)

Opakování

R má i klasické cykly, viz dokumentace:

  • for() – pokud se má kus kódu opakovat \(x\)-krát
  • while() – pokud se má kus kódu opakovat, dokud je splněná podmínka
  • repeat – pokud se má kus kódu opakovat do zastavení

Příliš se ale nepoužívají – R má pro opakování lepší struktury (viz příště).

Zastavení kódu

K zastavení běhu skriptu slouží funkce stop(): zastaví běh skriptu s chybou a vypíše svůj argument:

# kód se zastaví, pokud v není řetězec (zde se nezastaví)
v <- "ahoj"
if (!is.character(v))
    stop("v není řetězec!")

Výstrahy

Zprávy je možné do konzoly vypsat pomocí funkce message(), varování pomocí funkce warning():

if (!is.list(v))
    warning("Pozor: v není seznam!")
## Warning: Pozor: v není seznam!

Do konzoly je možné vypisovat i pomocí funkcí print(), cat() apod.

Zprávy o běhu kódu však vypisujte raději pomocí message() a warning(): jsou barevně odlišené a je možné je snadno potlačit.

K procvičení

Vylepšete funkci aabs(x) tak, aby skončila chybovou hláškou, pokud x není numerický skalár (tj. vektor délky 1).

(Funkce abs() samozřejmě pracuje s celými vektory.)

Seznam užitečných funkcí

Domácí úkol

Vytvořte funkci xMax(v, n), která vrátí \(n\)-tý největší prvek vektoru v. Vektor v musí být zadán vždy. Pokud je n zadáno, vrátí n-tý největší prvek; pokud n není zadáno, vrátí druhý největší prvek.

Funkce skončí chybou, pokud

  • n je zadáno, ale není to jedno celé číslo (pozor: nemusí být třída integer!, chyba "n isn't one round number!"),
  • n je menší než jedna nebo větší než délka vektoru v (chyba "n is out of bounds!")
  • v není numerický atomický vektor (chyba "v is not numeric atomic vector!")
  • nejsou hodnoty vektoru v unikátní, tj. některá hodnota se ve vektoru vyskytuje vícekrát (chyba "v doesn't have unique values!"); hint: funkce unique()

Při ukončení chybou musí funkce vypsat předepsanou chybovou hlášku.