--- title: "Objekty" author: "Michal Kvasnička" documentclass: article output: html_document: theme: cerulean toc: yes toc_float: yes pdf_document: default fontsize: 10pt classoption: a4paper --- # Objekty {#kap:objekty} R je ve své podstatě objektově orientovaný jazyk: vše, co žije v\ prostředí\ R, jsou objekty. R\ je však velmi zvláštní objektově orientovaný jazyk. Na rozdíl od jiných objektových jazyků nejsou objekty (v\ užším smyslu slova) součástí definice vlastního jazyka, ale jsou vytvořeny v\ rámci tohoto jazyka. V\ důsledku toho (a\ dlouhého vývoje\ R a jeho předchůdce, jazyka\ S) dnes v\ R existuje několik různých systémů objektů, z\ nichž nejvýznamnější je systém\ S3, který je nejpoužívanější a zároveň nejjednodušší. Zde se zaměříme právě na tento systém. (Vysvětlení ostatních systémů OOP v\ R najdete v\ @WickhamAdvR v\ kapitole\ 7, která je dostupná na http://adv-r.had.co.nz/OO-essentials.html.) V\ této kapitole se naučíte * základní terminologii, * vytvářet nové objekty, metody a generické funkce a * používat existující objekty. Cílem zde mimořádně není to, abyste vytvářeli vlastní objekty\ -- to není při běžné datové analýze většinou potřeba; cílem je zajistit, že budete mít povšechné povědomí o\ tom, jak se s\ objekty v\ R zachází. K\ tomu je užitečné vědět, jak jsou objekty a metody zhruba implementované. ## Základní pojmy objektově orientovaného programování (pro laiky) Základním pojmem OOP je *objekt*. Objekt je datová struktura s\ jasně definovanými metodami, jak s\ těmito daty zacházet. Každý objekt má určitou *třídu*. Třída (*class*) popisuje strukturu dat objektu, vztah k\ ostatním třídám objektů (v\ rámci dědičnosti) a to, jaké funkce (metody) se volají při práci s\ objektem. Dědičnost umožňuje vytvářet mezi objekty hierarchii předků a potomků, kde potomci dědí data a metody od svých předků, mohou je však modifikovat a přidávat k\ nim. Dědičnost vytváří hierarchii: potomci dědí vlastnosti a chování předků. Pokud potomek nemá implementovanou nějakou funkci, pak se použije funkce jeho předka. Např.\ objekt třídy *člověk* může obsahovat datové položky jako jméno, příjmení, výšku, váhu apod. Pro třídu *člověk* mohou být definovány nějaké funkce (metody), např.\ funkce, která spočítá BMI. Objekt třídy *manažer* může být logicky svázán s\ objektem třídy *člověk*\ -- obsahuje stejná data (může obsahovat i\ další, např.\ seznam podřízených). Stejně tak může využívat metody se stejnými jmény, které však mohou být implementovány odlišně. Logickou vazbu tříd *člověk* a *manažer* popisuje dědičnost: třída *manažer* je potomek třídy *člověk*. ## Systém\ S3 Systém\ S3 používá poměrně neobvyklý přístup k\ objektovému programování, tzv.\ *generic function OOP*. Tento přístup je jednoduchý (až primitivní), ale funguje velmi dobře a používá jej drtivá většina balíků v\ R. V\ normálních OOP jazycích patří objektu nebo třídě jak data, tak metody (funkce). V\ R však objektu patří jen data a jméno třídy; metody patří tzv.\ generické funkci. Nejjednodušší způsob, jak pochopit strukturu objektu, je nějaký objekt vytvořit. S3 nemá formální definici tříd. Objekt se vytvoří tak, že se nějaké základní datové struktuře (většinou seznamu) nastaví atribut `class` na hodnotu jména třídy: ```r foo <- list() # vytvoří prázdný seznam class(foo) <- "foo" # přiřadí mu třídu foo ``` Alternativně to jde provést naráz pomocí funkce `structure()`, která nastavuje danému objektu atributy: ```r foo <- structure(list(), class = "foo") ``` Nyní je proměnná `foo` objekt třídy *foo*: ```r class(foo) ``` ``` ## [1] "foo" ``` R\ nemá žádný mechanismus, který by kontroloval, zda je datová struktura objektu správná. Již vytvořenému objektu je možné přidat nebo ubrat datové sloty (technicky obvykle prvky seznamu) nebo změnit jeho třídu. Někdy se to hodí; nikdy to však nedělejte, pokud opravdu dobře nevíte, co děláte, jinak vznikne velmi těžko předvídatelné chování a těžko dohledatelné chyby. Aby se zajistilo, že je struktura objektu správná, je vhodné vytvořit *konstruktor*, tj.\ funkci, která vytváří objekt. (Funkce jako `numeric()` jsou také konstruktory.) Ukažme si to na příkladu. Budeme chtít mít objekty třídy *human*, které budou obsahovat datové položky `name`, `height` a `weight`. Nejdříve vytvoříme konstruktor, a\ pak jeden objekt: ```r human <- function(name, height, weight) # konstruktor structure(list(name = name, height = height, weight = weight), class = "human") adam <- human("Adam", 173, 63) # tvorba objektu pomocí konstruktoru human() ``` Dědičnost se zajistí tak, že atribut `class` je vektorem jmen několik tříd\ -- zleva doprava od potomků k\ předkům. Budeme např.\ chtít mít objekty třídy *manager*, které jsou potomky třídy *human*. Opět pro ně vytvoříme konstruktor a jeden objekt: ```r manager <- function(name, height, weight, rank) # konstruktor structure(list(name = name, height = height, weight = weight, rank = rank), class = c("manager", "human")) eva <- manager("Eve", 169, 52, "CEO") # objekt třídy manager ``` Eva nyní dědí vlastnosti člověka: ```r class(eva) ``` ``` ## [1] "manager" "human" ``` ```r inherits(eva, "manager") ``` ``` ## [1] TRUE ``` ```r inherits(eva, "human") ``` ``` ## [1] TRUE ``` Protože je většina objektů v\ S3 postavená pomocí seznamů, můžete zjistit strukturu objektu pomocí funkce `str()`. Hodnotu jednotlivých slotů objektů typu\ S3 můžete získat pomocí operátoru\ `$`: ```r str(eva) ``` ``` ## List of 4 ## $ name : chr "Eve" ## $ height: num 169 ## $ weight: num 52 ## $ rank : chr "CEO" ## - attr(*, "class")= chr [1:2] "manager" "human" ``` ```r eva$rank ``` ``` ## [1] "CEO" ``` Hlavní pointa systému\ S3 spočívá v\ tom, že stejně pojmenovaná funkce volá pro každou třídu objektu jinou metodu (jinak implementovanou funkci) přesně uzpůsobenou dané třídě dat. Funkcím, které to dokážou, se říká *generické funkce*. Příkladem generické funkce je funkce `print()`, která tiskne různé informace v\ závislosti na tom, jaká je třída objektu, kterou chceme vytisknout. Třída *human* zatím nemá definovanou žádnou metodu pro generickou funkci `print()`, proto zavolá metodu pro seznam: ```r print(adam) ``` ``` ## $name ## [1] "Adam" ## ## $height ## [1] 173 ## ## $weight ## [1] 63 ## ## attr(,"class") ## [1] "human" ``` Když vytvoříte novou třídu objektu, můžete vytvořit i\ novou metodu ke generické funkci tak, že vytvoříte funkci, jejíž jméno má tvar `jmeno_genericke_funkce.jmeno_tridy`, tj.\ jméno generické funkce oddělené tečkou od názvu třídy objektu. Metodu pro tisk objektů třídy *human* vytvoříme např.\ takto: ```r print.human <- function(x) cat("*** Human ***", paste("Name:", x$name), paste("Height:", x$height), paste("Weight:", x$weight), sep = "\n") print(adam) ``` ``` ## *** Human *** ## Name: Adam ## Height: 173 ## Weight: 63 ``` Protože třída *manager* je potomkem třídy *human*, dědí její chování. To znamená, že pokud tato třída nemá definovanou svou metodu `print()`, pak zavolá metodu svého předka, třídy *human*: ```r eva # implicitně se volá funkce print ``` ``` ## *** Human *** ## Name: Eve ## Height: 169 ## Weight: 52 ``` Novou generickou funkci vytvoříme tak, že vytvoříme funkci, která bude obsahovat jediný řádek `UseMethod("xxx")`, kde `xxx` je jméno nové generické funkce. Řekněme, že chceme vytvořit novou generickou funkci, která vrátí pro objekty třídy *human* jejich body mass index. Samozřejmě musíme vytvořit i\ příslušné metody. Řekněme, že budeme chtít mít různou metodu `bmi()` pro objekt třídy *human* a objekt třídy *manager*: ```r bmi <- function(x) # generická funkce UseMethod("bmi") bmi.human <- function(x) # metoda pro třídu human x$weight / (x$height / 100) ^ 2 bmi.manager <- function(x) # metoda pro třídu manager "classified" ``` Nyní můžeme zjistit, jaký mají Adam a Eva BMI (bohužel je informace o\ BMI manažerů tajná): ```r bmi(adam) ``` ``` ## [1] 21.04982 ``` ```r bmi(eva) ``` ``` ## [1] "classified" ``` Generická funkce může mít i\ implicitní metodu, která se použije v\ případě, že pro daný typ není žádná metoda k\ dispozici. Tato metoda se jmenuje `default`: ```r bmi.default <- function(x) "Unknown class" bmi(1) ``` ``` ## [1] "Unknown class" ``` (Bez definování implicitní metody by předchozí řádek skončil chybou.) ## Práce s\ objekty Ve většině případů nebudete vytvářet vlastní objekty\ -- stačí, když budete schopní zacházet s\ objekty, které vrátí funkce, které budete používat. Nejdříve ze všeho potřebujete poznat, zda je nějaká proměnná objekt v\ systému\ S3. Jsou tři možnosti, jak to můžete udělat: 1)\ můžete se podívat, zda je proměnná objekt pomocí funkce `is.object()`, a\ že se nejedná o\ objekt v\ systému\ S4, 2)\ můžete použít funkci `otype()` z\ balíku\ **pryr** nebo 3)\ se samozřejmě můžete podívat do dokumentace: ```r is.object(eva) & !isS4(eva) ``` ``` ## [1] TRUE ``` ```r pryr::otype(eva) # funkce z balíku pryr ``` ``` ## [1] "S3" ``` Často potřebujete zjistit, jak se jmenují jednotlivé položky objektu. Pokud je objekt v\ systému\ S3 postaven nad seznamem, což je nejčastější případ, můžete to udělat pomocí funkce `str()`. Když znáte jméno dané položky, můžete získat její obsah pomocí operátoru\ `$`: ```r str(eva) ``` ``` ## List of 4 ## $ name : chr "Eve" ## $ height: num 169 ## $ weight: num 52 ## $ rank : chr "CEO" ## - attr(*, "class")= chr [1:2] "manager" "human" ``` ```r eva$height ``` ``` ## [1] 169 ``` Někdy se hodí zjistit, jaké metody má daná třída k\ dispozici. K\ tomu slouží funkce `methods()`. Můžete ji použít dvě různými způsoby: 1)\ ke zjištění metod, které patří dané generické funkci, a\ 2)\ ke zjištění metod, které jsou k\ dispozici pro danou třídu: ```r head(methods("print")) # metody generické funkce print() -- jen prvních 6 metod ``` ``` ## [1] "print.acf" "print.AES" "print.anova" "print.aov" ## [5] "print.aovlist" "print.ar" ``` ```r methods(class = "human") # metody dostupné pro třídu human ``` ``` ## [1] bmi print ## see '?methods' for accessing help and source code ``` Jména konkrétních metod lze zjistit také pomocí funkce `apropos()`, když se požádá, aby hledala regulární výraz pro cokoli, co končí ".třída_objektu" (s\ regulárními výrazy se seznámíte v\ kapitole\ \@ref(kap:strings)): ```r apropos(".*\\.human") ``` ``` ## [1] "bmi.human" "print.human" ``` Důležité je také najít dokumentaci k\ dané metodě. Z\ dokumentace ke generické funkci se totiž nemusíte se dozvědět vše, co potřebujete. Pokud chcete nápovědu k\ tomu, jak se generická funkce chová pro daný objekt, hledejte dokumentaci k\ jeho metodě (bohužel ne všechny balíky dokumentují všechny metody, které implementují): ```r ?print.summary.lm # dokumentace k metodě pro tisk summary(m), kde m je objekt vráceny z\ lineární regrese ```