Title: | An Object Oriented System Meant to Become a Successor to S3 and S4 |
Version: | 0.2.0 |
Description: | A new object oriented programming system designed to be a successor to S3 and S4. It includes formal class, generic, and method specification, and a limited form of multiple dispatch. It has been designed and implemented collaboratively by the R Consortium Object-Oriented Programming Working Group, which includes representatives from R-Core, 'Bioconductor', 'Posit'/'tidyverse', and the wider R community. |
License: | MIT + file LICENSE |
URL: | https://rconsortium.github.io/S7/, https://github.com/RConsortium/S7 |
BugReports: | https://github.com/RConsortium/S7/issues |
Depends: | R (≥ 3.5.0) |
Imports: | utils |
Suggests: | bench, callr, covr, knitr, methods, rmarkdown, testthat (≥ 3.2.0), tibble |
VignetteBuilder: | knitr |
Config/build/compilation-database: | true |
Config/Needs/website: | sloop |
Config/testthat/edition: | 3 |
Config/testthat/parallel: | TRUE |
Config/testthat/start-first: | external-generic |
Encoding: | UTF-8 |
RoxygenNote: | 7.3.2 |
NeedsCompilation: | yes |
Packaged: | 2024-11-06 17:18:31 UTC; hadleywickham |
Author: | Object-Oriented Programming Working Group [cph],
Davis Vaughan [aut],
Jim Hester |
Maintainer: | Hadley Wickham <hadley@posit.co> |
Repository: | CRAN |
Date/Publication: | 2024-11-07 12:50:02 UTC |
Standard class specifications
Description
This is used as the interface between S7 and R's other OO systems, allowing you to use S7 classes and methods with base types, informal S3 classes, and formal S4 classes.
Usage
as_class(x, arg = deparse(substitute(x)))
Arguments
x |
A class specification. One of the following:
|
arg |
Argument name used when generating errors. |
Value
A standardised class: either NULL
, an S7 class, an S7 union,
as new_S3_class, or a S4 class.
Examples
as_class(class_logical)
as_class(new_S3_class("factor"))
S7 wrappers for base types
Description
The following S7 classes represent base types allowing them to be used within S7:
-
class_logical
-
class_integer
-
class_double
-
class_complex
-
class_character
-
class_raw
-
class_list
-
class_expression
-
class_name
-
class_call
-
class_function
-
class_environment
(can only be used for properties)
We also include three union types to model numerics, atomics, and vectors respectively:
-
class_numeric
is a union ofclass_integer
andclass_double
. -
class_atomic
is a union ofclass_logical
,class_numeric
,class_complex
,class_character
, andclass_raw
. -
class_vector
is a union ofclass_atomic
,class_list
, andclass_expression
. -
class_language
is a union ofclass_name
andclass_call
.
Usage
class_logical
class_integer
class_double
class_complex
class_character
class_raw
class_list
class_expression
class_name
class_call
class_function
class_environment
class_numeric
class_atomic
class_vector
class_language
Value
S7 classes wrapping around common base types and S3 classes.
Examples
class_integer
class_numeric
class_factor
S7 wrappers for key S3 classes
Description
S7 bundles S3 definitions for key S3 classes provided by the base packages:
-
class_data.frame
for data frames. -
class_Date
for dates. -
class_factor
for factors. -
class_POSIXct
,class_POSIXlt
andclass_POSIXt
for date-times. -
class_formula
for formulas.
Usage
class_factor
class_Date
class_POSIXct
class_POSIXlt
class_POSIXt
class_data.frame
class_formula
Dispatch on any class
Description
Use class_any
to register a default method that is called when no other
methods are matched.
Usage
class_any
Examples
foo <- new_generic("foo", "x")
method(foo, class_numeric) <- function(x) "number"
method(foo, class_any) <- function(x) "fallback"
foo(1)
foo("x")
Dispatch on a missing argument
Description
Use class_missing
to dispatch when the user has not supplied an argument,
i.e. it's missing in the sense of missing()
, not in the sense of
is.na()
.
Usage
class_missing
Value
Sentinel objects used for special types of dispatch.
Examples
foo <- new_generic("foo", "x")
method(foo, class_numeric) <- function(x) "number"
method(foo, class_missing) <- function(x) "missing"
method(foo, class_any) <- function(x) "fallback"
foo(1)
foo()
foo("")
Convert an object from one type to another
Description
convert(from, to)
is a built-in generic for converting an object from
one type to another. It is special in three ways:
It uses double-dispatch, because conversion depends on both
from
andto
.It uses non-standard dispatch because
to
is a class, not an object.It doesn't use inheritance for the
to
argument. To understand why, imagine you have written methods to objects of various types toclassParent
. If you then create a newclassChild
that inherits fromclassParent
, you can't expect the methods written forclassParent
to work because those methods will returnclassParent
objects, notclassChild
objects.
convert()
provides two default implementations:
When
from
inherits fromto
, it strips any properties thatfrom
possesses thatto
does not (downcasting).When
to
is a subclass offrom
's class, it creates a new object of classto
, copying over existing properties fromfrom
and initializing new properties ofto
(upcasting).
If you are converting an object solely for the purposes of accessing a method
on a superclass, you probably want super()
instead. See its docs for more
details.
S3 & S4
convert()
plays a similar role to the convention of defining as.foo()
functions/generics in S3, and to as()
/setAs()
in S4.
Usage
convert(from, to, ...)
Arguments
from |
An S7 object to convert. |
to |
An S7 class specification, passed to |
... |
Other arguments passed to custom |
Value
Either from
coerced to class to
, or an error if the coercion
is not possible.
Examples
Foo1 <- new_class("Foo1", properties = list(x = class_integer))
Foo2 <- new_class("Foo2", Foo1, properties = list(y = class_double))
# Downcasting: S7 provides a default implementation for coercing an object
# to one of its parent classes:
convert(Foo2(x = 1L, y = 2), to = Foo1)
# Upcasting: S7 also provides a default implementation for coercing an object
# to one of its child classes:
convert(Foo1(x = 1L), to = Foo2)
convert(Foo1(x = 1L), to = Foo2, y = 2.5) # Set new property
convert(Foo1(x = 1L), to = Foo2, x = 2L, y = 2.5) # Override existing and set new
# For all other cases, you'll need to provide your own.
try(convert(Foo1(x = 1L), to = class_integer))
method(convert, list(Foo1, class_integer)) <- function(from, to) {
from@x
}
convert(Foo1(x = 1L), to = class_integer)
# Note that conversion does not respect inheritance so if we define a
# convert method for integer to foo1
method(convert, list(class_integer, Foo1)) <- function(from, to) {
Foo1(x = from)
}
convert(1L, to = Foo1)
# Converting to Foo2 will still error
try(convert(1L, to = Foo2))
# This is probably not surprising because foo2 also needs some value
# for `@y`, but it definitely makes dispatch for convert() special
Find a method for an S7 generic
Description
method()
takes a generic and class signature and performs method dispatch
to find the corresponding method implementation. This is rarely needed
because you'll usually rely on the the generic to do dispatch for you (via
S7_dispatch()
). However, this introspection is useful if you want to see
the implementation of a specific method.
Usage
method(generic, class = NULL, object = NULL)
Arguments
generic |
A generic function, i.e. an S7 generic, an external generic, an S3 generic, or an S4 generic. |
class , object |
Perform introspection either with a |
Value
Either a function with class S7_method
or an error if no
matching method is found.
See Also
method_explain()
to explain why a specific method was picked.
Examples
# Create a generic and register some methods
bizarro <- new_generic("bizarro", "x")
method(bizarro, class_numeric) <- function(x) rev(x)
method(bizarro, class_factor) <- function(x) {
levels(x) <- rev(levels(x))
x
}
# Printing the generic shows the registered method
bizarro
# And you can use method() to inspect specific implementations
method(bizarro, class = class_integer)
method(bizarro, object = 1)
method(bizarro, class = class_factor)
# errors if method not found
try(method(bizarro, class = class_data.frame))
try(method(bizarro, object = "x"))
Explain method dispatch
Description
method_explain()
shows all possible methods that a call to a generic
might use, which ones exist, and which one will actually be called.
Note that method dispatch uses a string representation of each class in the class hierarchy. Each class system uses a slightly different convention to avoid ambiguity.
S7:
pkg::class
orclass
S4:
S4/pkg::class
orS4/class
S3:
class
Usage
method_explain(generic, class = NULL, object = NULL)
Arguments
generic |
A generic function, i.e. an S7 generic, an external generic, an S3 generic, or an S4 generic. |
class , object |
Perform introspection either with a |
Value
Nothing; this function is called for it's side effects.
Examples
Foo1 <- new_class("Foo1")
Foo2 <- new_class("Foo2", Foo1)
add <- new_generic("add", c("x", "y"))
method(add, list(Foo2, Foo1)) <- function(x, y) c(2, 1)
method(add, list(Foo1, Foo1)) <- function(x, y) c(1, 1)
method_explain(add, list(Foo2, Foo2))
Register an S7 method for a generic
Description
A generic defines the interface of a function. Once you have created a
generic with new_generic()
, you provide implementations for specific
signatures by registering methods with method<-
.
The goal is for method<-
to be the single function you need when working
with S7 generics or S7 classes. This means that as well as registering
methods for S7 classes on S7 generics, you can also register methods for
S7 classes on S3 or S4 generics, and S3 or S4 classes on S7 generics.
But this is not a general method registration function: at least one of
generic
and signature
needs to be from S7.
Note that if you are writing a package, you must call methods_register()
in your .onLoad
. This ensures that all methods are dynamically registered
when needed.
Usage
method(generic, signature) <- value
Arguments
generic |
A generic function, i.e. an S7 generic, an external generic, an S3 generic, or an S4 generic. |
signature |
A method signature. For S7 generics that use single dispatch, this must be one of the following:
For S7 generics that use multiple dispatch, this must be a list of any of the above types. For S3 generics, this must be a single S7 class. For S4 generics, this must either be an S7 class, or a list that includes at least one S7 class. |
value |
A function that implements the generic specification for the
given |
Value
The generic
, invisibly.
Examples
# Create a generic
bizarro <- new_generic("bizarro", "x")
# Register some methods
method(bizarro, class_numeric) <- function(x) rev(x)
method(bizarro, new_S3_class("data.frame")) <- function(x) {
x[] <- lapply(x, bizarro)
rev(x)
}
# Using a generic calls the methods automatically
bizarro(head(mtcars))
Register methods in a package
Description
When using S7 in a package you should always call methods_register()
when
your package is loaded. This ensures that methods are registered as needed
when you implement methods for generics (S3, S4, and S7) in other packages.
(This is not strictly necessary if you only register methods for generics
in your package, but it's better to include it and not need it than forget
to include it and hit weird errors.)
Usage
methods_register()
Value
Nothing; called for its side-effects.
Examples
.onLoad <- function(...) {
S7::methods_register()
}
Define a new S7 class
Description
A class specifies the properties (data) that each of its objects will possess. The class, and its parent, determines which method will be used when an object is passed to a generic.
Learn more in vignette("classes-objects")
Usage
new_class(
name,
parent = S7_object,
package = topNamespaceName(parent.frame()),
properties = list(),
abstract = FALSE,
constructor = NULL,
validator = NULL
)
new_object(.parent, ...)
Arguments
name |
The name of the class, as a string. The result of calling
|
parent |
The parent class to inherit behavior from. There are three options:
|
package |
Package name. This is automatically resolved if the class is
defined in a package, and Note, if the class is intended for external use, the constructor should be
exported. Learn more in |
properties |
A named list specifying the properties (data) that
belong to each instance of the class. Each element of the list can
either be a type specification (processed by |
abstract |
Is this an abstract class? An abstract class can not be instantiated. |
constructor |
The constructor function. In most cases, you can rely on the default constructor, which will generate a function with one argument for each property. A custom constructor should call |
validator |
A function taking a single argument, The job of a validator is to determine whether the object is valid, i.e. if the current property values form an allowed combination. The types of the properties are always automatically validated so the job of the validator is to verify that the values of individual properties are ok (i.e. maybe a property should have length 1, or should always be positive), or that the combination of values of multiple properties is ok. It is called after construction and whenever any property is set. The validator should return See |
.parent , ... |
Parent object and named properties used to construct the object. |
Value
A object constructor, a function that can be used to create objects of the given class.
Examples
# Create an class that represents a range using a numeric start and end
Range <- new_class("Range",
properties = list(
start = class_numeric,
end = class_numeric
)
)
r <- Range(start = 10, end = 20)
r
# get and set properties with @
r@start
r@end <- 40
r@end
# S7 automatically ensures that properties are of the declared types:
try(Range(start = "hello", end = 20))
# But we might also want to use a validator to ensure that start and end
# are length 1, and that start is < end
Range <- new_class("Range",
properties = list(
start = class_numeric,
end = class_numeric
),
validator = function(self) {
if (length(self@start) != 1) {
"@start must be a single number"
} else if (length(self@end) != 1) {
"@end must be a single number"
} else if (self@end < self@start) {
"@end must be great than or equal to @start"
}
}
)
try(Range(start = c(10, 15), end = 20))
try(Range(start = 20, end = 10))
r <- Range(start = 10, end = 20)
try(r@start <- 25)
Generics in other packages
Description
You need an explicit external generic when you want to provide methods for a generic (S3, S4, or S7) that is defined in another package, and you don't want to take a hard dependency on that package.
The easiest way to provide methods for generics in other packages is
import the generic into your NAMESPACE
. This, however, creates a hard
dependency, and sometimes you want a soft dependency, only registering the
method if the package is already installed. new_external_generic()
allows
you to provide the minimal needed information about a generic so that methods
can be registered at run time, as needed, using methods_register()
.
Note that in tests, you'll need to explicitly call the generic from the
external package with pkg::generic()
.
Usage
new_external_generic(package, name, dispatch_args, version = NULL)
Arguments
package |
Package the generic is defined in. |
name |
Name of generic, as a string. |
dispatch_args |
Character vector giving arguments used for dispatch. |
version |
An optional version the package must meet for the method to be registered. |
Value
An S7 external generic, i.e. a list with class
S7_external_generic
.
Examples
MyClass <- new_class("MyClass")
your_generic <- new_external_generic("stats", "median", "x")
method(your_generic, MyClass) <- function(x) "Hi!"
Define a new generic
Description
A generic function uses different implementations (methods) depending on
the class of one or more arguments (the signature). Create a new generic
with new_generic()
then use method<- to add methods to it.
Method dispatch is performed by S7_dispatch()
, which must always be
included in the body of the generic, but in most cases new_generic()
will
generate this for you.
Learn more in vignette("generics-methods")
Usage
new_generic(name, dispatch_args, fun = NULL)
S7_dispatch()
Arguments
name |
The name of the generic. This should be the same as the object that you assign it to. |
dispatch_args |
A character vector giving the names of one or more arguments used to find the method. |
fun |
An optional specification of the generic, which must call
The |
Value
An S7 generic, i.e. a function with class S7_generic
.
Dispatch arguments
The arguments that are used to pick the method are called the dispatch arguments. In most cases, this will be one argument, in which case the generic is said to use single dispatch. If it consists of more than one argument, it's said to use multiple dispatch.
There are two restrictions on the dispatch arguments: they must be the first
arguments to the generic and if the generic uses ...
, it must occur
immediately after the dispatch arguments.
See Also
new_external_generic()
to define a method for a generic
in another package without taking a strong dependency on it.
Examples
# A simple generic with methods for some base types and S3 classes
type_of <- new_generic("type_of", dispatch_args = "x")
method(type_of, class_character) <- function(x, ...) "A character vector"
method(type_of, new_S3_class("data.frame")) <- function(x, ...) "A data frame"
method(type_of, class_function) <- function(x, ...) "A function"
type_of(mtcars)
type_of(letters)
type_of(mean)
# If you want to require that methods implement additional arguments,
# you can use a custom function:
mean2 <- new_generic("mean2", "x", function(x, ..., na.rm = FALSE) {
S7_dispatch()
})
method(mean2, class_numeric) <- function(x, ..., na.rm = FALSE) {
if (na.rm) {
x <- x[!is.na(x)]
}
sum(x) / length(x)
}
# You'll be warned if you forget the argument:
method(mean2, class_character) <- function(x, ...) {
stop("Not supported")
}
Define a new property
Description
A property defines a named component of an object. Properties are
typically used to store (meta) data about an object, and are often
limited to a data of a specific class
.
By specifying a getter
and/or setter
, you can make the property
"dynamic" so that it's computed when accessed or has some non-standard
behaviour when modified. Dynamic properties are not included as an argument
to the default class constructor.
See the "Properties: Common Patterns" section in vignette("class-objects")
for more examples.
Usage
new_property(
class = class_any,
getter = NULL,
setter = NULL,
validator = NULL,
default = NULL,
name = NULL
)
Arguments
class |
Class that the property must be an instance of.
See |
getter |
An optional function used to get the value. The function
should take If a property has a getter but doesn't have a setter, it is read only. |
setter |
An optional function used to set the value. The function
should take |
validator |
A function taking a single argument, The job of a validator is to determine whether the property value is valid.
It should return The validator will be called after the |
default |
When an object is created and the property is not supplied,
what should it default to? If |
name |
Property name, primarily used for error messages. Generally
don't need to set this here, as it's more convenient to supply as a
the element name when defining a list of properties. If both |
Value
An S7 property, i.e. a list with class S7_property
.
Examples
# Simple properties store data inside an object
Pizza <- new_class("Pizza", properties = list(
slices = new_property(class_numeric, default = 10)
))
my_pizza <- Pizza(slices = 6)
my_pizza@slices
my_pizza@slices <- 5
my_pizza@slices
your_pizza <- Pizza()
your_pizza@slices
# Dynamic properties can compute on demand
Clock <- new_class("Clock", properties = list(
now = new_property(getter = function(self) Sys.time())
))
my_clock <- Clock()
my_clock@now; Sys.sleep(1)
my_clock@now
# This property is read only, because there is a 'getter' but not a 'setter'
try(my_clock@now <- 10)
# Because the property is dynamic, it is not included as an
# argument to the default constructor
try(Clock(now = 10))
args(Clock)
Declare an S3 class
Description
To use an S3 class with S7, you must explicitly declare it using
new_S3_class()
because S3 lacks a formal class definition.
(Unless it's an important base class already defined in base_s3_classes.)
Usage
new_S3_class(class, constructor = NULL, validator = NULL)
Arguments
class |
S3 class vector (i.e. what |
constructor |
An optional constructor that can be used to create
objects of the specified class. This is only needed if you wish to
have an S7 class inherit from an S3 class or to use the S3 class as a
property without a default. It must be specified in the
same way as a S7 constructor: the first argument should be All arguments to the constructor should have default values so that when the constructor is called with no arguments, it returns returns an "empty", but valid, object. |
validator |
An optional validator used by A validator is a single argument function that takes the object to
validate and returns |
Value
An S7 definition of an S3 class, i.e. a list with class
S7_S3_class
.
Method dispatch, properties, and unions
There are three ways of using S3 with S7 that only require the S3 class vector:
Registering a S3 method for an S7 generic.
Restricting an S7 property to an S3 class.
Using an S3 class in an S7 union.
This is easy, and you can usually include the new_S3_class()
call inline:
method(my_generic, new_S3_class("factor")) <- function(x) "A factor" new_class("MyClass", properties = list(types = new_S3_class("factor"))) new_union("character", new_S3_class("factor"))
Extending an S3 class
Creating an S7 class that extends an S3 class requires more work. You'll
also need to provide a constructor for the S3 class that follows S7
conventions. This means the first argument to the constructor should be
.data
, and it should be followed by one argument for each attribute used
by the class.
This can be awkward because base S3 classes are usually heavily wrapped for user
convenience and no low level constructor is available. For example, the
factor class is an integer vector with a character vector of levels
, but
there's no base R function that takes an integer vector of values and
character vector of levels, verifies that they are consistent, then
creates a factor object.
You may optionally want to also provide a validator
function which will
ensure that validate()
confirms the validity of any S7 classes that build
on this class. Unlike an S7 validator, you are responsible for validating
the types of the attributes.
The following code shows how you might wrap the base Date class.
A Date is a numeric vector with class Date
that can be constructed with
.Date()
.
S3_Date <- new_S3_class("Date", function(.data = integer()) { .Date(.data) }, function(self) { if (!is.numeric(self)) { "Underlying data must be numeric" } } )
Examples
# No checking, just used for dispatch
Date <- new_S3_class("Date")
my_generic <- new_generic("my_generic", "x")
method(my_generic, Date) <- function(x) "This is a date"
my_generic(Sys.Date())
Define a class union
Description
A class union represents a list of possible classes. You can create it
with new_union(a, b, c)
or a | b | c
. Unions can be used in two
places:
To allow a property to be one of a set of classes,
new_property(class_integer | Range)
. The defaultdefault
value for the property will be the constructor of the first object in the union. This means if you want to create an "optional" property (i.e. one that can beNULL
or of a specified type), you'll need to write (e.g.)NULL | class_integer
.As a convenient short-hand to define methods for multiple classes.
method(foo, X | Y) <- f
is short-hand formethod(foo, X) <- f; method(foo, Y) <- foo
S7 includes built-in unions for "numeric" (integer and double vectors), "atomic" (logical, numeric, complex, character, and raw vectors) and "vector" (atomic vectors, lists, and expressions).
Usage
new_union(...)
Arguments
... |
The classes to include in the union. See |
Value
An S7 union, i.e. a list with class S7_union
.
Examples
logical_or_character <- new_union(class_logical, class_character)
logical_or_character
# or with shortcut syntax
logical_or_character <- class_logical | class_character
Foo <- new_class("Foo", properties = list(x = logical_or_character))
Foo(x = TRUE)
Foo(x = letters[1:5])
try(Foo(1:3))
bar <- new_generic("bar", "x")
# Use built-in union
method(bar, class_atomic) <- function(x) "Hi!"
bar
bar(TRUE)
bar(letters)
try(bar(NULL))
Get/set a property
Description
-
prop(x, "name")
/prop@name
get the value of the a property, erroring if it the property doesn't exist. -
prop(x, "name") <- value
/prop@name <- value
set the value of a property.
Usage
prop(object, name)
prop(object, name, check = TRUE) <- value
object@name
Arguments
object |
An object from a S7 class |
name |
The name of the parameter as a character. Partial matching is not performed. |
check |
If |
value |
A new value for the property. The object is automatically checked for validity after the replacement is done. |
Value
prop()
and @
return the value of the property.
prop<-()
and @<-
are called for their side-effects and return
the modified object, invisibly.
Examples
Horse <- new_class("Horse", properties = list(
name = class_character,
colour = class_character,
height = class_numeric
))
lexington <- Horse(colour = "bay", height = 15, name = "Lex")
lexington@colour
prop(lexington, "colour")
lexington@height <- 14
prop(lexington, "height") <- 15
Property introspection
Description
-
prop_names(x)
returns the names of the properties -
prop_exists(x, "prop")
returnsTRUE
iifx
has propertyprop
.
Usage
prop_names(object)
prop_exists(object, name)
Arguments
object |
An object from a S7 class |
name |
The name of the parameter as a character. Partial matching is not performed. |
Value
prop_names()
returns a character vector; prop_exists()
returns
a single TRUE
or FALSE
.
Examples
Foo <- new_class("Foo", properties = list(a = class_character, b = class_integer))
f <- Foo()
prop_names(f)
prop_exists(f, "a")
prop_exists(f, "c")
Get/set multiple properties
Description
-
props(x)
returns all properties. -
props(x) <- list(name1 = val1, name2 = val2)
modifies an existing object by setting multiple properties simultaneously. -
set_props(x, name1 = val1, name2 = val2)
creates a copy of an existing object with new values for the specified properties.
Usage
props(object, names = prop_names(object))
props(object) <- value
set_props(object, ...)
Arguments
object |
An object from a S7 class |
names |
A character vector of property names to retrieve. Default is all properties. |
value |
A named list of values. The object is checked for validity only after all replacements are performed. |
... |
Name-value pairs given property to modify and new value. |
Value
A named list of property values.
Examples
Horse <- new_class("Horse", properties = list(
name = class_character,
colour = class_character,
height = class_numeric
))
lexington <- Horse(colour = "bay", height = 15, name = "Lex")
props(lexington)
props(lexington) <- list(height = 14, name = "Lexington")
lexington
Register an S7 class with S4
Description
If you want to use method<- to register an method for an S4 generic with
an S7 class, you need to call S4_register()
once.
Usage
S4_register(class, env = parent.frame())
Arguments
class |
An S7 class created with |
env |
Expert use only. Environment where S4 class will be registered. |
Value
Nothing; the function is called for its side-effect.
Examples
methods::setGeneric("S4_generic", function(x) {
standardGeneric("S4_generic")
})
Foo <- new_class("Foo")
S4_register(Foo)
method(S4_generic, Foo) <- function(x) "Hello"
S4_generic(Foo())
Retrieve the S7 class of an object
Description
Given an S7 object, find it's class.
Usage
S7_class(object)
Arguments
object |
The S7 object |
Value
An S7 class.
Examples
Foo <- new_class("Foo")
S7_class(Foo())
Get/set underlying "base" data
Description
When an S7 class inherits from an existing base type, it can be useful to work with the underlying object, i.e. the S7 object stripped of class and properties.
Usage
S7_data(object)
S7_data(object, check = TRUE) <- value
Arguments
object |
An object from a S7 class |
check |
If |
value |
Object used to replace the underlying data. |
Value
S7_data()
returns the data stored in the base object;
S7_data<-()
is called for its side-effects and returns object
invisibly.
Examples
Text <- new_class("Text", parent = class_character)
y <- Text(c(foo = "bar"))
y
S7_data(y)
S7_data(y) <- c("a", "b")
y
Does this object inherit from an S7 class?
Description
-
S7_inherits()
returnsTRUE
orFALSE
. -
check_is_S7()
throws an error ifx
isn't the specifiedclass
.
Usage
S7_inherits(x, class = NULL)
check_is_S7(x, class = NULL, arg = deparse(substitute(x)))
Arguments
x |
An object |
class |
An S7 class or |
arg |
Argument name used in error message. |
Value
-
S7_inherits()
returns a singleTRUE
orFALSE
. -
check_is_S7()
returns nothing; it's called for its side-effects.
Note
Starting with R 4.3.0, base::inherits()
can accept an S7 class as
the second argument, supporting usage like inherits(x, Foo)
.
Examples
Foo1 <- new_class("Foo1")
Foo2 <- new_class("Foo2")
S7_inherits(Foo1(), Foo1)
check_is_S7(Foo1())
check_is_S7(Foo1(), Foo1)
S7_inherits(Foo1(), Foo2)
try(check_is_S7(Foo1(), Foo2))
if (getRversion() >= "4.3.0")
inherits(Foo1(), Foo1)
Base S7 class
Description
The base class from which all S7 classes eventually inherit from.
Usage
S7_object()
Value
The base S7 object.
Examples
S7_object
Force method dispatch to use a superclass
Description
super(from, to)
causes the dispatch for the next generic to use the method
for the superclass to
instead of the actual class of from
. It's needed
when you want to implement a method in terms of the implementation of its
superclass.
S3 & S4
super()
performs a similar role to NextMethod()
in S3 or
methods::callNextMethod()
in S4, but is much more explicit:
The super class that
super()
will use is known when writesuper()
(i.e. statically) as opposed to when the generic is called (i.e. dynamically).All arguments to the generic are explicit; they are not automatically passed along.
This makes super()
more verbose, but substantially easier to
understand and reason about.
super()
in S3 generics
Note that you can't use super()
in methods for an S3 generic.
For example, imagine that you have made a subclass of "integer":
MyInt <- new_class("MyInt", parent = class_integer, package = NULL)
Now you go to write a custom print method:
method(print, MyInt) <- function(x, ...) { cat("<MyInt>") print(super(x, to = class_integer)) } MyInt(10L) #> <MyInt>super(<MyInt>, <integer>)
This doesn't work because print()
isn't an S7 generic so doesn't
understand how to interpret the special object that super()
produces.
While you could resolve this problem with NextMethod()
(because S7 is
implemented on top of S3), we instead recommend using S7_data()
to extract
the underlying base object:
method(print, MyInt) <- function(x, ...) { cat("<MyInt>") print(S7_data(x)) } MyInt(10L) #> <MyInt>[1] 10
Usage
super(from, to)
Arguments
from |
An S7 object to cast. |
to |
An S7 class specification, passed to |
Value
An S7_super
object which should always be passed
immediately to a generic. It has no other special behavior.
Examples
Foo1 <- new_class("Foo1", properties = list(x = class_numeric, y = class_numeric))
Foo2 <- new_class("Foo2", Foo1, properties = list(z = class_numeric))
total <- new_generic("total", "x")
method(total, Foo1) <- function(x) x@x + x@y
# This won't work because it'll be stuck in an infinite loop:
method(total, Foo2) <- function(x) total(x) + x@z
# We could write
method(total, Foo2) <- function(x) x@x + x@y + x@z
# but then we'd need to remember to update it if the implementation
# for total(<Foo1>) ever changed.
# So instead we use `super()` to call the method for the parent class:
method(total, Foo2) <- function(x) total(super(x, to = Foo1)) + x@z
total(Foo2(1, 2, 3))
# To see the difference between convert() and super() we need a
# method that calls another generic
bar1 <- new_generic("bar1", "x")
method(bar1, Foo1) <- function(x) 1
method(bar1, Foo2) <- function(x) 2
bar2 <- new_generic("bar2", "x")
method(bar2, Foo1) <- function(x) c(1, bar1(x))
method(bar2, Foo2) <- function(x) c(2, bar1(x))
obj <- Foo2(1, 2, 3)
bar2(obj)
# convert() affects every generic:
bar2(convert(obj, to = Foo1))
# super() only affects the _next_ call to a generic:
bar2(super(obj, to = Foo1))
Validate an S7 object
Description
validate()
ensures that an S7 object is valid by calling the validator
provided in new_class()
. This is done automatically when constructing new
objects and when modifying properties.
valid_eventually()
disables validation, modifies the object, then
revalidates. This is useful when a sequence of operations would otherwise
lead an object to be temporarily invalid, or when repeated property
modification causes a performance bottleneck because the validator is
relatively expensive.
valid_implicitly()
does the same but does not validate the object at the
end. It should only be used rarely, and in performance critical code where
you are certain a sequence of operations cannot produce an invalid object.
Usage
validate(object, recursive = TRUE, properties = TRUE)
valid_eventually(object, fun)
valid_implicitly(object, fun)
Arguments
object |
An S7 object |
recursive |
If |
properties |
If |
fun |
A function to call on the object before validation. |
Value
Either object
invisibly if valid, otherwise an error.
Examples
# A range class might validate that the start is less than the end
Range <- new_class("Range",
properties = list(start = class_double, end = class_double),
validator = function(self) {
if (self@start >= self@end) "start must be smaller than end"
}
)
# You can't construct an invalid object:
try(Range(1, 1))
# And you can't create an invalid object with @<-
r <- Range(1, 2)
try(r@end <- 1)
# But what if you want to move a range to the right?
rightwards <- function(r, x) {
r@start <- r@start + x
r@end <- r@end + x
r
}
# This function doesn't work because it creates a temporarily invalid state
try(rightwards(r, 10))
# This is the perfect use case for valid_eventually():
rightwards <- function(r, x) {
valid_eventually(r, function(object) {
object@start <- object@start + x
object@end <- object@end + x
object
})
}
rightwards(r, 10)
# Alternatively, you can set multiple properties at once using props<-,
# which validates once at the end
rightwards <- function(r, x) {
props(r) <- list(start = r@start + x, end = r@end + x)
r
}
rightwards(r, 20)