setIs {methods} | R Documentation |
Specify a Superclass Explicitly
Description
setIs
is an explicit alternative
to the contains=
argument to setClass
. It is
only needed to create relations with explicit test or coercion.
These have not proved to be of much practical value, so this
function should not likely be needed in applications.
Where the programming goal is to define methods for transforming one
class of objects to another, it is usually better practice to call
setAs()
, which requires the transformations to be done explicitly.
Usage
setIs(class1, class2, test=NULL, coerce=NULL, replace=NULL,
by = character(), where = topenv(parent.frame()), classDef =,
extensionObject = NULL, doComplete = TRUE)
Arguments
class1 , class2 |
the names of the classes between which |
coerce , replace |
functions optionally supplied to coerce the object to
|
test |
a conditional relationship is defined by supplying this function. Conditional relations are discouraged and are not included in selecting methods. See the details section below. The remaining arguments are for internal use and/or usually omitted. |
extensionObject |
alternative to the |
doComplete |
when |
by |
In a call to |
where |
In a call to |
classDef |
Optional class definition for |
Details
Arranging for a class to inherit from another class is a key tool in programming. In R, there are three basic techniques, the first two providing what is called “simple” inheritance, the preferred form:
-
By the
contains=
argument in a call tosetClass
. This is and should be the most common mechanism. It arranges that the new class contains all the structure of the existing class, and in particular all the slots with the same class specified. The resulting class extension is defined to besimple
, with important implications for method definition (see the section on this topic below). -
Making
class1
a subclass of a virtual class either by a call tosetClassUnion
to make the subclass a member of a new class union, or by a call tosetIs
to add a class to an existing class union or as a new subclass of an existing virtual class. In either case, the implication should be that methods defined for the class union or other superclass all work correctly for the subclass. This may depend on some similarity in the structure of the subclasses or simply indicate that the superclass methods are defined in terms of generic functions that apply to all the subclasses. These relationships are also generally simple. -
Supplying
coerce
andreplace
arguments tosetAs
. R allows arbitrary inheritance relationships, using the same mechanism for defining coerce methods by a call tosetAs
. The difference between the two is simply thatsetAs
will require a call toas
for a conversion to take place, whereas after the call tosetIs
, objects will be automatically converted to the superclass.The automatic feature is the dangerous part, mainly because it results in the subclass potentially inheriting methods that do not work. See the section on inheritance below. If the two classes involved do not actually inherit a large collection of methods, as in the first example below, the danger may be relatively slight.
If the superclass inherits methods where the subclass has only a default or remotely inherited method, problems are more likely. In this case, a general recommendation is to use the
setAs
mechanism instead, unless there is a strong counter reason. Otherwise, be prepared to override some of the methods inherited.
With this caution given, the rest of this section describes what
happens when coerce=
and replace=
arguments are supplied
to setIs
.
The coerce
and replace
arguments are functions that
define how to coerce a class1
object to class2
, and
how to replace the part of the subclass object that corresponds to
class2
. The first of these is a function of one argument
which should be from
, and the second of two arguments
(from
, value
). For details, see the section on coerce
functions below .
When by
is specified, the coerce process first coerces to
this class and then to class2
. It's unlikely you
would use the by
argument directly, but it is used in defining
cached information about classes.
The value returned (invisibly) by
setIs
is the revised class definition of class1
.
Coerce, replace, and test functions
The coerce
argument is a function that turns a
class1
object into a class2
object. The
replace
argument is a function of two arguments that modifies a class1
object (the first argument) to replace the part of it that
corresponds to class2
(supplied as value
, the second
argument). It then returns the modified object as the value of the
call. In other words, it acts as a replacement method to
implement the expression as(object, class2) <- value
.
The easiest way to think of the coerce
and replace
functions is by thinking of the case that class1
contains class2
in the usual sense, by including the slots of
the second class. (To repeat, in this situation you would not call
setIs
, but the analogy shows what happens when you do.)
The coerce
function in this case would just make a
class2
object by extracting the corresponding slots from the
class1
object. The replace
function would replace in
the class1
object the slots corresponding to class2
,
and return the modified object as its value.
For additional discussion of these functions, see
the documentation of the
setAs
function. (Unfortunately, argument
def
to that function corresponds to argument coerce
here.)
The inheritance relationship can also be conditional, if a function is supplied as the
test
argument. This should be a function of one argument
that returns TRUE
or FALSE
according to whether the
object supplied satisfies the relation is(object, class2)
.
Conditional relations between
classes are discouraged in general because they require a per-object
calculation to determine their validity. They cannot be applied
as efficiently as ordinary relations and tend to make the code that
uses them harder to interpret. NOTE: conditional inheritance
is not used to dispatch methods. Methods for conditional
superclasses will not be inherited. Instead, a method for the
subclass should be defined that tests the conditional relationship.
Inherited methods
A method written for a particular signature (classes matched to one or more formal arguments to the function) naturally assumes that the objects corresponding to the arguments can be treated as coming from the corresponding classes. The objects will have all the slots and available methods for the classes.
The code that selects and dispatches the methods ensures that this
assumption is correct. If the inheritance was “simple”, that
is, defined by one or more uses of the contains=
argument in
a call to setClass
, no extra work is generally
needed. Classes are inherited from the superclass, with the same
definition.
When inheritance is defined by a general call to
setIs
, extra computations are required. This form of
inheritance implies that the subclass does not just contain
the slots of the superclass, but instead requires the explicit call
to the coerce and/or replace method. To ensure correct computation,
the inherited method is supplemented by calls to as
before the body of the method is evaluated.
The calls to as
generated in this case have the
argument strict = FALSE
, meaning that extra information can
be left in the converted object, so long as it has all the
appropriate slots. (It's this option that allows simple subclass
objects to be used without any change.) When you are writing your
coerce method, you may want to take advantage of that option.
Methods inherited through non-simple extensions can result in ambiguities
or unexpected selections. If class2
is a specialized class
with just a few applicable methods, creating the inheritance
relation may have little effect on the behavior of class1
.
But if class2
is a class with many methods, you may
find that you now inherit some undesirable methods for
class1
, in some cases, fail to inherit expected methods.
In the second example below, the non-simple inheritance from class
"factor"
might be assumed to inherit S3 methods via that
class. But the S3 class is ambiguous, and in fact is
"character"
rather than "factor"
.
For some generic functions, methods inherited by non-simple
extensions are either known to be invalid or sufficiently likely to
be so that the generic function has been defined to exclude such
inheritance. For example initialize
methods must
return an object of the target class; this is straightforward if the
extension is simple, because no change is made to the argument
object, but is essentially impossible. For this reason, the generic
function insists on only simple extensions for inheritance. See the
simpleInheritanceOnly
argument to setGeneric
for the mechanism. You can use this mechanism when defining new
generic functions.
If you get into problems with functions that do allow non-simple
inheritance, there are two basic choices. Either
back off from the setIs
call and settle for explicit coercing
defined by a call to setAs
; or, define explicit
methods involving class1
to override the bad inherited
methods. The first choice is the safer, when there are serious
problems.
References
Chambers, John M. (2016) Extending R, Chapman & Hall. (Chapters 9 and 10.)
Examples
## Two examples of setIs() with coerce= and replace= arguments
## The first one works fairly well, because neither class has many
## inherited methods do be disturbed by the new inheritance
## The second example does NOT work well, because the new superclass,
## "factor", causes methods to be inherited that should not be.
## First example:
## a class definition (see \link{setClass} for class "track")
setClass("trackCurve", contains = "track",
slots = c( smooth = "numeric"))
## A class similar to "trackCurve", but with different structure
## allowing matrices for the "y" and "smooth" slots
setClass("trackMultiCurve",
slots = c(x="numeric", y="matrix", smooth="matrix"),
prototype = structure(list(), x=numeric(), y=matrix(0,0,0),
smooth= matrix(0,0,0)))
## Automatically convert an object from class "trackCurve" into
## "trackMultiCurve", by making the y, smooth slots into 1-column matrices
setIs("trackCurve",
"trackMultiCurve",
coerce = function(obj) {
new("trackMultiCurve",
x = obj@x,
y = as.matrix(obj@y),
smooth = as.matrix(obj@smooth))
},
replace = function(obj, value) {
obj@y <- as.matrix(value@y)
obj@x <- value@x
obj@smooth <- as.matrix(value@smooth)
obj})
## Second Example:
## A class that adds a slot to "character"
setClass("stringsDated", contains = "character",
slots = c(stamp="POSIXt"))
## Convert automatically to a factor by explicit coerce
setIs("stringsDated", "factor",
coerce = function(from) factor(from@.Data),
replace= function(from, value) {
from@.Data <- as.character(value); from })
ll <- sample(letters, 10, replace = TRUE)
ld <- new("stringsDated", ll, stamp = Sys.time())
levels(as(ld, "factor"))
levels(ld) # will be NULL--see comment in section on inheritance above.
## In contrast, a class that simply extends "factor"
## has no such ambiguities
setClass("factorDated", contains = "factor",
slots = c(stamp="POSIXt"))
fd <- new("factorDated", factor(ll), stamp = Sys.time())
identical(levels(fd), levels(as(fd, "factor")))