The R7 package is a new OOP system designed to be a successor to S3 and S4. It has been designed and implemented collaboratively by the RConsortium Object-Oriented Programming Working Group, which includes representatives from R-Core, BioConductor, RStudio/tidyverse, and the wider R community.
The long-term goal of this project is to merge R7 in to base R. For now, you can experiment by installing the in-development version from GitHub:
# install.packages("remotes")
remotes::install_github("r-consortium/OOP-WG")
This section gives a very brief overview of the entirety of R7. Learn
more of the basics in vignettte("R7")
, the details of method dispatch
in vignette("dispatch")
, and compatibility with S3 and S4 in
vignette("compatibility")
.
library(R7)
R7 classes have a formal definition, which includes a list of properties
and an optional validator. Use new_class()
to define a class:
range <- new_class("range",
properties = list(
start = class_double,
end = class_double
),
validator = function(self) {
if (length(self@start) != 1) {
"@start must be length 1"
} else if (length(self@end) != 1) {
"@end must be length 1"
} else if (self@end < self@start) {
"@end must be greater than or equal to @start"
}
}
)
new_class()
returns the class object, which is also the constructor
you use to create instances of the class:
x <- range(start = 1, end = 10)
x
#> <range>
#> @ start: num 1
#> @ end : num 10
The data possessed by an object is called its properties. Use @
to
get and set properties:
x@start
#> [1] 1
x@end <- 20
x
#> <range>
#> @ start: num 1
#> @ end : num 20
Properties are automatically validated against the type declared in
new_class()
(double
in this case), and with the class validator:
x@end <- "x"
#> Error: <range>@end must be <double>, not <character>
x@end <- -1
#> Error: <range> object is invalid:
#> - @end must be greater than or equal to @start
Like S3 and S4, R7 uses functional OOP where methods belong to
generic functions, and method calls look like all other function
calls: generic(object, arg2, arg3)
. This style is called functional
because from the outside it looks like a regular function call, and
internally the components are also functions.
Use new_generic()
to create a new generic: the first argument is the
generic name (used in error messages) and the second gives the arguments
used for dispatch. The third, and optional argument, supplies the body
of the generic. This is only needed if your generic has additional
arguments that aren’t used for method dispatch.
inside <- new_generic("inside", "x", function(x, y) {
# Actually finds and calls the appropriate method
R7_dispatch()
})
Once you have a generic, you can define a method for a specific class
with method<-
:
# Add a method for our class
method(inside, range) <- function(x, y) {
y >= x@start & y <= x@end
}
inside
#> <R7_generic> function (x, y) with 1 methods:
#> 1: method(inside, range)
inside(x, c(0, 5, 10, 15))
#> [1] FALSE TRUE TRUE TRUE
You can use method<-
to register methods for base types on R7
generics:
method(inside, class_numeric) <- function(x, y) {
y >= min(x) & y <= max(x)
}
And register methods for R7 classes on S3 or S4 generics:
method(format, range) <- function(x, ...) {
paste0("[", x@start, ", ", x@end, "]")
}
format(x)
#> [1] "[1, 20]"
method(mean, range) <- function(x, ...) {
(x@start + x@end) / 2
}
mean(x)
#> [1] 10.5