In this lab, we will learn how to write methods for S3 generics.
Goal: by the end of this lab, you should be able to write your own S3 generic methods and functions.
First, we’ll load the sloop
package to get some
information about the S3 system.
library(sloop)
Since we’ve already loaded the tidyverse
, there are lots
of S3 generic function and their methods already available to us.
For example, there are many methods for the print()
generic available:
s3_methods_generic("print")
In fact, there are so many that we might as well restrict our attention to those relevant to tibbles and data.frames.
s3_methods_generic("print") %>%
filter(str_detect(class, "tbl|data.frame"))
## Filtering a tbl_df object with 385 rows...
Other common S3 generics include summary()
and
plot()
:
s3_methods_generic("summary")
s3_methods_generic("plot")
s3_methods_generic()
to find its available
methods. [Note that you might have to load other packages to find the
methods or the generic that you recall.]In addition to listing the methods for a particular generic, we can find all the methods that are defined for a particular class.
s3_methods_class("tbl_df")
Note that while the methods for a particular generic are most likely written by many different developers in many different packages, the methods for a particular class are most likely written by the same set of developers of a single package.
If you are ever unsure of which method is actually being run when you
invoke a generic, use s3_dispatch()
to figure out what is
going on.
s3_dispatch(print(starwars))
## print.tbl_df
## => print.tbl
## * print.data.frame
## * print.default
s3_dispatch(print(mtcars))
## => print.data.frame
## * print.default
Why do the previous two examples show a different list of available methods?
Why doesn’t print.data.frame()
get called on
starwars
?
First, we’ll learn about two fun packages: emoji
and
crayon
.
emoji
The emoji
package allows you print emoji via the emoji()
function.
install.packages("emoji")
To display an emoji, call it by name:
library(emoji)
emoji("star")
## [1] "⭐"
emoji("dog")
## [1] "🐶"
emoji("basketball")
## [1] "🏀"
You can see the full list of available emojis in the tibble returned
by emojis
.
%>% View() emojis
emoji()
to print it
to the console.crayon
The crayon
package allows you to print in color in the console. For a
complicated example of what you can do, consider the
tidyverse
logo:
tidyverse_logo()
## ⬢ __ _ __ . ⬡ ⬢ .
## / /_(_)__/ /_ ___ _____ _______ ___
## / __/ / _ / // / |/ / -_) __(_-</ -_)
## \__/_/\_,_/\_, /|___/\__/_/ /___/\__/
## ⬢ . /___/ ⬡ . ⬢
For a simpler, example, you can write your name in blue. Be sure to
wrap the text in cat()
(or message()
) to send
it to the console.
cat(crayon::blue("Ben"))
## Ben
crayon
package and write
something to the console in that color.Now we will define a simple print()
method that will
only be invoked for objects of class beanumber
, which is my
GitHub username. This method will simply print a colorful message to the
console, and then invoke the NextMethod()
available.
<- function(x, ...) {
print.beanumber cat(
::magenta(
crayon"Ben",
::emoji("heart"),
emoji"s programming in R!\n"
)
)NextMethod()
}
To use it, we just have to create an object of type
beanumber
, and then call print()
. Remember
that in R you can simply modify the class attribute of any object!
class(starwars) <- c("beanumber", class(starwars))
starwars
Note that this method works on objects of any underlying class!!
class(Titanic)
## [1] "table"
class(Titanic) <- c("beanumber", class(Titanic))
Titanic
## Ben ❤️ s programming in R!
## , , Age = Child, Survived = No
##
## Sex
## Class Male Female
## 1st 0 0
## 2nd 0 0
## 3rd 35 17
## Crew 0 0
##
## , , Age = Adult, Survived = No
##
## Sex
## Class Male Female
## 1st 118 4
## 2nd 154 13
## 3rd 387 89
## Crew 670 3
##
## , , Age = Child, Survived = Yes
##
## Sex
## Class Male Female
## 1st 5 1
## 2nd 11 13
## 3rd 13 14
## Crew 0 0
##
## , , Age = Adult, Survived = Yes
##
## Sex
## Class Male Female
## 1st 57 140
## 2nd 14 80
## 3rd 75 76
## Crew 192 20
print()
method for objects having class
[your GitHub username].In the previous section, we defined a method for an existing generic function. Now, we will define a new generic function.
<- function(x, ...) {
print_emoji UseMethod("print_emoji")
}
Since we haven’t defined any methods yet, the function won’t actually work!
print_emoji(starwars)
## Error in UseMethod("print_emoji"): no applicable method for 'print_emoji' applied to an object of class "c('beanumber', 'tbl_df', 'tbl', 'data.frame')"
However, we can start by writing a default
method that
will simply call the existing print()
generic.
<- function(x, ...) {
print_emoji.default print(x, ...)
}
Now our print_emoji()
function will work.
print_emoji(starwars)
## Ben ❤️ s programming in R!
## # A tibble: 87 × 15
## name height mass hair_…¹ skin_…² eye_c…³ birth…⁴ sex gender homew…⁵
## <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr>
## 1 Luke Skywa… 172 77 blond fair blue 19 male mascu… Tatooi…
## 2 C-3PO 167 75 <NA> gold yellow 112 none mascu… Tatooi…
## 3 R2-D2 96 32 <NA> white,… red 33 none mascu… Naboo
## 4 Darth Vader 202 136 none white yellow 41.9 male mascu… Tatooi…
## 5 Leia Organa 150 49 brown light brown 19 fema… femin… Aldera…
## 6 Owen Lars 178 120 brown,… light blue 52 male mascu… Tatooi…
## 7 Beru White… 165 75 brown light blue 47 fema… femin… Tatooi…
## 8 R5-D4 97 32 <NA> white,… red NA none mascu… Tatooi…
## 9 Biggs Dark… 183 84 black light brown 24 male mascu… Tatooi…
## 10 Obi-Wan Ke… 182 77 auburn… fair blue-g… 57 male mascu… Stewjon
## # … with 77 more rows, 5 more variables: species <chr>, films <list>,
## # vehicles <list>, starships <list>, is_bald <lgl>, and abbreviated variable
## # names ¹hair_color, ²skin_color, ³eye_color, ⁴birth_year, ⁵homeworld
In the default
method for
print_emoji()
, can we use NextMethod()
instead
of print()
? Why or why not?
Write a print_emoji()
method for
data.frame
s.
Prompt: Paste the code from your
print_emoji()
method to the#emojis
channel. What does it do?