| Title: | Formal Methods and Classes |
|---|---|
| Description: | Formally defined methods and classes for R objects, plus other programming tools, as described in the reference. |
| Authors: | R Core Team |
| Maintainer: | R Core Team <[email protected]> |
| License: | Part of R 4.5.0 |
| Version: | 4.5.0 |
| Built: | 2025-04-11 20:15:33 UTC |
| Source: | base |
Formally defined methods and classes for R objects, plusother programming tools, as described in the references.
This package provides the “S4” or “S version 4”approach to methods and classes in a functional language.
For basic use of the techniques, start withIntroduction andfollow the links there to the key functions for programming, notablysetClass andsetMethod.
Some specific topics:
Creating one, seesetClass;examining definitions, seegetClassDef andclassRepresentation; inheritance and coercing,seeis andas
Basic programming, seesetGeneric; the class of objects, seegenericFunction; other functions to examine ormanipulate them, seeGenericFunctions.
Using classes, seesetOldClass; methods,seeMethods_for_S3.
SeeReferenceClasses.
SeesetClassUnion.
These pages will have additional links to related topics.
For a completelist of functions and classes, uselibrary(help="methods").
R Core Team
Maintainer: R Core Team[email protected]
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (Chapter 10 has some additional details.)
A named list providing instructions for turning builtin and specialfunctions into generic functions.
Functions in R that are defined as.Primitive(<name>) are notsuitable for formal methods, because they lack the basic reflectanceproperty. You can't find the argument list for these functions byexamining the function object itself.
Future versions of R may fix this by attaching a formal argument listto the corresponding function. While generally the names of argumentsare not checked by the internal code implementing the function, thenumber of arguments frequently is.
In any case, some definition of a formal argument list is needed ifusers are to define methods for these functions. In particular, ifmethods are to be merged from multiple packages, the different setsof methods need to agree on the formal arguments.
In the absence of reflectance, this list provides the relevantinformation via a dummy function associated with each of the knownspecials for which methods are allowed.
At the same, the list flags those specials for which methods aremeaningless (e.g.,for) or just a very bad idea (e.g.,.Primitive).
A generic function created viasetMethod, forexample, for one of these special functions will have the argumentlist from.BasicFunsList. If no entry exists, the argumentlist(x, ...) is assumed.
Coerce an object to a given class.
as(object, Class, strict=TRUE, ext)as(object, Class) <- valueas(object, Class, strict=TRUE, ext)as(object, Class)<- value
object | anyR object. |
Class | the name of the class to which |
strict | logical flag. If If |
value | The value to use to modify |
ext | an optional objectdefining how |
as(object)returns the version of this object coerced to be the givenClass. When used in the replacement form on the left ofan assignment, the portion of the object corresponding toClass is replaced byvalue.
The operation ofas() in either form depends on thedefinition of coerce methods. Methods are defined automaticallywhen the two classes are related by inheritance; that is, whenone of the classes is a subclass of the other.
Coerce methods are also predefined for basic classes (including allthe types of vectors, functions and a few others).
Beyond these two sources of methods, further methods are definedby calls to thesetAs function. See thatdocumentation also for details of how coerce methods work. UseshowMethods(coerce) for a list of all currently defined methods, as in theexample below.
Methods are pre-defined for coercing any object to one of the basicdatatypes. For example,as(x, "numeric") uses the existingas.numeric function. These and all other existing methodscan be listed as shown in the example.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
If you think of usingtry(as(x, cl)), considercanCoerce(x, cl) instead.
## Show all the existing methods for as()showMethods("coerce")## Show all the existing methods for as()showMethods("coerce")
Formal classes exist corresponding to the basic R object types, allowingthese types to be used in method signatures, as slots in classdefinitions, and to be extended by new classes.
### The following are all basic vector classes.### They can appear as class names in method signatures,### in calls to as(), is(), and new()."character""complex""double""expression""integer""list""logical""numeric""single""raw"### the class"vector"### is a virtual class, extended by all the above### the class"S4"### is an object type for S4 objects that do not extend### any of the basic vector classes. It is a virtual class.### The following are additional basic classes"NULL" # NULL objects"function" # function objects, including primitives"externalptr" # raw external pointers for use in C code"ANY" # virtual classes used by the methods package itself"VIRTUAL""missing""namedList" # the alternative to "list" that preserves # the names attribute### The following are all basic vector classes.### They can appear as class names in method signatures,### in calls to as(), is(), and new()."character""complex""double""expression""integer""list""logical""numeric""single""raw"### the class"vector"### is a virtual class, extended by all the above### the class"S4"### is an object type for S4 objects that do not extend### any of the basic vector classes. It is a virtual class.### The following are additional basic classes"NULL"# NULL objects"function"# function objects, including primitives"externalptr"# raw external pointers for use in C code"ANY"# virtual classes used by the methods package itself"VIRTUAL""missing""namedList"# the alternative to "list" that preserves# the names attribute
If a class is not virtual (see section inClasses_Details),objects can be created by calls of the formnew(Class, ...),whereClass is the quoted class name, and the remainingarguments if any are objects to be interpreted as vectors of thisclass. Multiple arguments will be concatenated.
The class"expression" is slightly odd, in that the ...arguments willnot be evaluated; therefore, don't enclose themin a call toquote().
Note that class"list" is a pure vector. Although lists withnames go back to the earliest versions of S, they are an extensionof the vector concept in that they have an attribute (which can nowbe a slot) and which is eitherNULL or a character vector ofthe same length as the vector. If you want to guarantee that listnames are preserved, use class"namedList", rather than"list". Objects from this class must have a names attribute,corresponding to slot"names",of type"character". Internally, R treats names forlists specially, which makes it impractical to have the corresponding slot inclass"namedList" be a union of character names andNULL.
The basic classes include classes for the basic R types. Note thatobjects of these types will not usually be S4 objects(isS4 will returnFALSE), although objects fromclasses that contain the basic class will be S4 objects, still withthe same type. The type asreturned bytypeof will sometimes differ from the class,either just from a choice of terminology (type"symbol" andclass"name", for example) or because there is not a one-to-onecorrespondence between class and type (most of the classes thatinherit from class"language" have type"language", for example).
The vector classes extend"vector", directly.
Methods are defined to coerce arbitrary objects tothe vector classes, by calling the corresponding basic function, forexample,as(x, "numeric") callsas.numeric(x).
A call tocallGeneric can only appear inside a methoddefinition. It then results in a call to the current genericfunction. The value of that call is the value ofcallGeneric.While it can be called from any method, it is useful and typicallyused in methods for group generic functions.
callGeneric(...)callGeneric(...)
... | Optionally, the arguments to the function in its next call. If no arguments are included in the call to |
The name and package of the current generic function is stored in theenvironment of the method definition object. This name is looked upand the corresponding function called.
The statement that passing no arguments tocallGeneric causesthe generic function to be called with the current arguments ismore precisely as follows. Arguments that were missing in the currentcall are still missing (remember that"missing" is a validclass in a method signature). For a formal argument, sayx, thatappears in the original call, there is a corresponding argument in thegenerated call equivalent tox = x. In effect, thismeans that the generic function sees the same actual arguments, butarguments are evaluated only once.
UsingcallGeneric with no arguments is prone to creatinginfinite recursion, unless one of the arguments in the signature hasbeen modified in the current method so that a different method is selected.
The value returned by the new call.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (Section 10.4 for some details.)
GroupGenericFunctions for other informationabout group generic functions;Methods_Details for the general behaviorof method dispatch
## the method for group generic function Ops## for signature( e1="structure", e2="vector")function (e1, e2){ value <- callGeneric([email protected], e2) if (length(value) == length(e1)) {[email protected] <- value e1 } else value}## For more examples## Not run: showMethods("Ops", includeDefs = TRUE)## End(Not run)## the method for group generic function Ops## for signature( e1="structure", e2="vector")function(e1, e2){ value<- callGeneric(e1@.Data, e2)if(length(value)== length(e1)){ e1@.Data<- value e1}else value}## For more examples## Not run:showMethods("Ops", includeDefs=TRUE)## End(Not run)
A call tocallNextMethod can only appear inside a methoddefinition. It then results in a call to the first inherited methodafter the current method, with the arguments to the current methodpassed down to the next method. The value of that method call is thevalue ofcallNextMethod.
callNextMethod(...)callNextMethod(...)
... | Optionally, the arguments to the function in its next call(but note that the dispatch is as in the detailed description below;the arguments have no effect on selecting the next method.) If no arguments are included in the call to Calling with no arguments is often the natural way to use |
The ‘next’ method (i.e., the first inherited method) is definedto be that method whichwould have been called if the currentmethod did not exist. This is more-or-less literally what happens: Thecurrent method (to be precise, the method with signature given by thedefined slot of the method from whichcallNextMethod iscalled) is deleted from a copy of the methods for the current generic,andselectMethod is called to find the next method (theresult is cached in the method object where the call occurred, so the search typicallyhappens only once per session per combination of argument classes).
The next method is defined from thesignature of the currentmethod, not from the actual classes of the arguments.In particular, modifying any of the arguments has no effect on theselection.As a result, the selected next method can be called with invalidarguments if the calling function assigns objects of a differentclass before thecallNextMethod() call.Be careful of any assignments to such arguments.
It is possible for the selection of the next method to be ambiguous,even though the original set of methods was consistent.See the section “Ambiguous Selection”.
The statement that the method is called with the current arguments ismore precisely as follows. Arguments that were missing in the currentcall are still missing (remember that"missing" is a validclass in a method signature). For a formal argument, sayx, thatappears in the original call, there is a corresponding argument in thenext method call equivalent tox = x. In effect, thismeans that the next method sees the same actual arguments, butarguments are evaluated only once.
The value returned by the selected method.
There are two fairly common situations in which the choice of a nextmethod is ambiguous, even when the original set of methods uniquelydefines all method selection unambiguously.In these situations,callNextMethod() should be replaced,either by a call to a specific function or by recalling the genericwith different arguments.
The most likely situation arises with methods for binary operators,typically through one of the group generic functions.See the example for class"rnum" below.Examples of this sort usually require three methods: two for the casethat the first or the second argument comes from the class, and athird for the case that both arguments come from the class.If that last method usescallNextMethod, the other two methodsare equally valid. The ambiguity is exactly the same that requireddefining the two-argument method in the first place.
In fact, the two possibilities are equally valid conceptually as wellas formally.As in the example below, the logic of the application usually requiresselecting a computation explicitly or else calling the genericfunction with modified arguments to select an appropriate method.
The other likely source of ambiguity arises from a class that inheritsdirectly from more than one other class (a “mixin” in standardterminology).If the generic has methods corresponding to both superclasses, amethod for the current class is again needed to resolve ambiguity.UsingcallNextMethod will again reimpose the ambiguity.Again, some explicit choice has to be made in the calling methodinstead.
These ambiguities are not the result of bad design, but they dorequire workarounds.Other ambiguities usually reflect inconsistencies in the tree ofinheritances, such as a class appearing in more than one place amongthe superclasses.Such cases should be rare, but with the independent definition ofclasses in multiple packages, they can't be ruled out.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
callGeneric to call the generic function with thecurrent dispatch rules (typically for a group generic function);Methods_Details for the general behavior of method dispatch.
## callNextMethod() used for the Math, Math2 group generic functions## A class to automatically round numeric results to "d" digitsrnum <- setClass("rnum", slots = c(d = "integer"), contains = "numeric")## Math functions operate on the rounded numbers, return a plain## vector. The next method will always be the default, usually a primitive.setMethod("Math", "rnum", function(x) callNextMethod(round(as.numeric(x), x@d)))setMethod("Math2", "rnum", function(x, digits) callNextMethod(round(as.numeric(x), x@d), digits))## Examples of callNextMethod with two arguments in the signature.## For arithmetic and one rnum with anything, callNextMethod with no arguments## round the full accuracy result, and return as plain vectorsetMethod("Arith", c(e1 ="rnum"), function(e1, e2) as.numeric(round(callNextMethod(), e1@d)))setMethod("Arith", c(e2 ="rnum"), function(e1, e2) as.numeric(round(callNextMethod(), e2@d)))## A method for BOTH arguments from "rnum" would be ambiguous## for callNextMethod(): the two methods above are equally valid.## The method chooses the smaller number of digits,## and then calls the generic function, postponing the method selection## until it's not ambiguous.setMethod("Arith", c(e1 ="rnum", e2 = "rnum"), function(e1, e2) { if(e1@d <= e2@d) callGeneric(e1, as.numeric(e2)) else callGeneric(as.numeric(e1), e2) })## For comparisons, callNextMethod with the rounded argumentssetMethod("Compare", c(e1 = "rnum"), function(e1, e2) callNextMethod(round(e1, e1@d), round(e2, e1@d)))setMethod("Compare", c(e2 = "rnum"), function(e1, e2) callNextMethod(round(e1, e2@d), round(e2, e2@d)))## similarly to the Arith case, the method for two "rnum" objects## can not unambiguously use callNextMethod(). Instead, we rely on## The rnum() method inhertited from Math2 to return plain vectors.setMethod("Compare", c(e1 ="rnum", e2 = "rnum"), function(e1, e2) { d <- min(e1@d, e2@d) callGeneric(round(e1, d), round(e2, d)) })set.seed(867)x1 <- rnum(10*runif(5), d=1L)x2 <- rnum(10*runif(5), d=2L)x1+1x2*2x1-x2## Simple examples to illustrate callNextMethod with and without argumentsB0 <- setClass("B0", slots = c(s0 = "numeric"))## and a function to illustrate callNextMethodf <- function(x, text = "default") { str(x) # print a summary paste(text, ":", class(x))}setGeneric("f")setMethod("f", "B0", function(x, text = "B0") { cat("B0 method called with s0 =", x@s0, "\n") callNextMethod()})b0 <- B0(s0 = 1)## call f() with 2 arguments: callNextMethod passes both to the default methodf(b0, "first test")## call f() with 1 argument: the default "B0" is not passed by callNextMethodf(b0)## Now, a class that extends B0, with no methods for f()B1 <- setClass("B1", slots = c(s1 = "character"), contains = "B0")b1 <- B1(s0 = 2, s1 = "Testing B1")## the two cases work as before, by inheriting the "B0" methodf(b1, b1@s1)f(b1)B2 <- setClass("B2", contains = "B1")## And, a method for "B2" that calls with explicit arguments.## Note that the method selection in callNextMethod## uses the class of the *argument* to consistently select the "B0" methodsetMethod("f", "B2", function(x, text = "B1 method") { y <- B1(s0 = -x@s0, s1 ="Modified x") callNextMethod(y, text)})b2 <- B2(s1 = "Testing B2", s0 = 10)f(b2, b2@s1)f(b2)## Be careful: the argument passed must be legal for the method selected## Although the argument here is numeric, it's still the "B0" method that's calledsetMethod("f", "B2", function(x, text = "B1 method") { callNextMethod(x@s0, text)})## Now the call will cause an error:tryCatch(f(b2), error = function(e) cat(e$message,"\n"))## callNextMethod() used for the Math, Math2 group generic functions## A class to automatically round numeric results to "d" digitsrnum<- setClass("rnum", slots= c(d="integer"), contains="numeric")## Math functions operate on the rounded numbers, return a plain## vector. The next method will always be the default, usually a primitive.setMethod("Math","rnum",function(x) callNextMethod(round(as.numeric(x), x@d)))setMethod("Math2","rnum",function(x, digits) callNextMethod(round(as.numeric(x), x@d), digits))## Examples of callNextMethod with two arguments in the signature.## For arithmetic and one rnum with anything, callNextMethod with no arguments## round the full accuracy result, and return as plain vectorsetMethod("Arith", c(e1="rnum"),function(e1, e2) as.numeric(round(callNextMethod(), e1@d)))setMethod("Arith", c(e2="rnum"),function(e1, e2) as.numeric(round(callNextMethod(), e2@d)))## A method for BOTH arguments from "rnum" would be ambiguous## for callNextMethod(): the two methods above are equally valid.## The method chooses the smaller number of digits,## and then calls the generic function, postponing the method selection## until it's not ambiguous.setMethod("Arith", c(e1="rnum", e2="rnum"),function(e1, e2){if(e1@d<= e2@d) callGeneric(e1, as.numeric(e2))else callGeneric(as.numeric(e1), e2)})## For comparisons, callNextMethod with the rounded argumentssetMethod("Compare", c(e1="rnum"),function(e1, e2) callNextMethod(round(e1, e1@d), round(e2, e1@d)))setMethod("Compare", c(e2="rnum"),function(e1, e2) callNextMethod(round(e1, e2@d), round(e2, e2@d)))## similarly to the Arith case, the method for two "rnum" objects## can not unambiguously use callNextMethod(). Instead, we rely on## The rnum() method inhertited from Math2 to return plain vectors.setMethod("Compare", c(e1="rnum", e2="rnum"),function(e1, e2){ d<- min(e1@d, e2@d) callGeneric(round(e1, d), round(e2, d))})set.seed(867)x1<- rnum(10*runif(5), d=1L)x2<- rnum(10*runif(5), d=2L)x1+1x2*2x1-x2## Simple examples to illustrate callNextMethod with and without argumentsB0<- setClass("B0", slots= c(s0="numeric"))## and a function to illustrate callNextMethodf<-function(x, text="default"){ str(x)# print a summary paste(text,":", class(x))}setGeneric("f")setMethod("f","B0",function(x, text="B0"){ cat("B0 method called with s0 =", x@s0,"\n") callNextMethod()})b0<- B0(s0=1)## call f() with 2 arguments: callNextMethod passes both to the default methodf(b0,"first test")## call f() with 1 argument: the default "B0" is not passed by callNextMethodf(b0)## Now, a class that extends B0, with no methods for f()B1<- setClass("B1", slots= c(s1="character"), contains="B0")b1<- B1(s0=2, s1="Testing B1")## the two cases work as before, by inheriting the "B0" methodf(b1, b1@s1)f(b1)B2<- setClass("B2", contains="B1")## And, a method for "B2" that calls with explicit arguments.## Note that the method selection in callNextMethod## uses the class of the *argument* to consistently select the "B0" methodsetMethod("f","B2",function(x, text="B1 method"){ y<- B1(s0=-x@s0, s1="Modified x") callNextMethod(y, text)})b2<- B2(s1="Testing B2", s0=10)f(b2, b2@s1)f(b2)## Be careful: the argument passed must be legal for the method selected## Although the argument here is numeric, it's still the "B0" method that's calledsetMethod("f","B2",function(x, text="B1 method"){ callNextMethod(x@s0, text)})## Now the call will cause an error:tryCatch(f(b2), error=function(e) cat(e$message,"\n"))
Test if an object can be coerced to a given S4 class.Maybe useful insideif() to ensure that callingas(object, Class) will find a method.
canCoerce(object, Class)canCoerce(object, Class)
object | anyR object, typically of a formal S4 class. |
Class | an S4 class (see |
a scalar logical,TRUE if there is acoerce method(as defined by e.g.setAs) for the signature(from = class(object), to = Class).
as,setAs,selectMethod,setClass,
m <- matrix(pi, 2,3)canCoerce(m, "numeric") # TRUEcanCoerce(m, "array") # TRUEm<- matrix(pi,2,3)canCoerce(m,"numeric")# TRUEcanCoerce(m,"array")# TRUE
Combine two matrix-likeR objects by columns (cbind2)or rows (rbind2). These are (S4) generic functions with defaultmethods.
cbind2(x, y, ...)rbind2(x, y, ...)cbind2(x, y,...)rbind2(x, y,...)
x | anyR object, typically matrix-like. |
y | anyR object, typically similar to |
... | optional arguments for methods. |
The main use ofcbind2 (rbind2) is to be calledrecursively bycbind() (rbind()) when both ofthese requirements are met:
There is at least one argument that is an S4 object, and
S3 dispatch fails (see the Dispatch section undercbind).
The methods oncbind2 andrbind2 effectively define thetype promotion policy when combining a heterogeneous set ofarguments. The homogeneous case, where all objects derive from some S4class, can be handled via S4 dispatch on the... argument viaan externally defined S4cbind (rbind) generic.
Since (for legacy reasons) S3 dispatch is attempted first, it isgenerally a good idea to additionally define an S3 method oncbind (rbind) for the S4 class. The S3 method will beinvoked when the arguments include objects of the S4 class, along witharguments of classes for which no S3 method exists. Also, in case thereis an argument that selects a different S3 method (like the one fordata.frame), this S3 method serves to introduce an ambiguity indispatch that triggers the recursive fallback tocbind2(rbind2). Otherwise, the other S3 method would be called, whichmay not be appropriate.
A matrix (or matrix like object) combining the columns (or rows) ofx andy. Note that methods must constructcolnames andrownames from thecorresponding column and row names ofx andy (but notfrom deparsing argument names such as incbind(..., deparse.level = d) for).
signature(x = "ANY", y = "ANY")the default methodusingR's internal code.
signature(x = "ANY", y = "missing")the default methodfor one argument usingR's internal code.
cbind2(1:3, 4)m <- matrix(3:8, 2,3, dimnames=list(c("a","b"), LETTERS[1:3]))cbind2(1:2, m) # keeps dimnames from m## rbind() and cbind() now make use of rbind2()/cbind2() methodssetClass("Num", contains="numeric")setMethod("cbind2", c("Num", "missing"), function(x,y, ...) { cat("Num-miss--meth\n"); as.matrix(x)})setMethod("cbind2", c("Num","ANY"), function(x,y, ...) { cat("Num-A.--method\n") ; cbind(getDataPart(x), y, ...) })setMethod("cbind2", c("ANY","Num"), function(x,y, ...) { cat("A.-Num--method\n") ; cbind(x, getDataPart(y), ...) })a <- new("Num", 1:3)trace("cbind2")cbind(a)cbind(a, four=4, 7:9)# calling cbind2() twicecbind(m,a, ch=c("D","E"), a*3)cbind(1,a, m) # ok with a warninguntrace("cbind2")cbind2(1:3,4)m<- matrix(3:8,2,3, dimnames=list(c("a","b"), LETTERS[1:3]))cbind2(1:2, m)# keeps dimnames from m## rbind() and cbind() now make use of rbind2()/cbind2() methodssetClass("Num", contains="numeric")setMethod("cbind2", c("Num","missing"),function(x,y,...){ cat("Num-miss--meth\n"); as.matrix(x)})setMethod("cbind2", c("Num","ANY"),function(x,y,...){ cat("Num-A.--method\n"); cbind(getDataPart(x), y,...)})setMethod("cbind2", c("ANY","Num"),function(x,y,...){ cat("A.-Num--method\n"); cbind(x, getDataPart(y),...)})a<- new("Num",1:3)trace("cbind2")cbind(a)cbind(a, four=4,7:9)# calling cbind2() twicecbind(m,a, ch=c("D","E"), a*3)cbind(1,a, m)# ok with a warninguntrace("cbind2")
You have navigated to an old link to documentation of S4 classes.
For basic use of classes and methods, seeIntroduction; tocreate new class definitions, seesetClass; fortechnical details on S4 classes, seeClasses_Details.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Class definitions are objects that contain the formal definition of aclass ofR objects, usually referred to as an S4 class, todistinguish them from the informal S3 classes.This document gives an overview of S4 classes; fordetails of the class representation objects, see help for the classclassRepresentation.
When a class is defined, an object is stored that contains theinformation about that class. The object, known as themetadata defining the class, is not stored under the name ofthe class (to allow programmers to write generating functions ofthat name), but under a specially constructed name.To examine the class definition, callgetClass. Theinformation in the metadata object includes:
The data contained in an object from an S4 class is defined bytheslots in the class definition.
Each slot in an object is a component of the object;like components (that is, elements) of alist, these may be extracted and set, using thefunctionslot() or more often the operator"@". However, theydiffer from list components in important ways.First, slots can only be referred to by name, not by position,and there is no partial matching of names as with list elements.
All the objects from a particular class have the same set of slotnames; specifically, the slot names that are contained in theclass definition. Each slot in each object always is an objectof theclass specified for this slot in the definition of the current class.The word “is” corresponds to theR function of the samename (is), meaning that the class of the object inthe slot must be the same as the class specified in thedefinition, or some class that extends the one in thedefinition (asubclass).
A special slot name,.Data, stands for the‘data part’ of the object. An object from a class with adata part is defined by specifying that the class contains oneof theR object types or one of the special pseudo-classes,matrix orarray, usually because the definition ofthe class, or of one of its superclasses, has included the typeor pseudo-class in itscontains argument. A secondspecial slot name,.xData, is used to enable inheritancefrom abnormal types such as"environment"See the section on inheriting from non-S4 classesfor details on the representation andfor the behavior of S3 methods with objects from these classes.
Some slot names correspond to attributes used in old-style S3objects and inR objects without an explicit class, forexample, thenames attribute. If you define a class forwhich that attribute will be set, such as a subclass of namedvectors, you should include"names" as a slot. See thedefinition of class"namedList" for an example. Using thenames() assignment to set such names will generate awarning if there is no names slot and an error if the object inquestion is not a vector type. A slot called"names" canbe used anywhere, but only if it is assigned as a slot, not viathe defaultnames() assignment.
The definition of a class includes thesuperclasses —theclasses that this class extends. AclassFancy, say, extends a classSimple if anobject from theFancy class has all the capabilities oftheSimple class (and probably some more as well). Inparticular, and very usefully, any method defined to work for aSimple object can be applied to aFancy object aswell.
This relationship is expressed equivalently by saying thatSimple is a superclass ofFancy, or thatFancy is a subclass ofSimple.
The direct superclasses of a class are those superclassesexplicitly defined. Direct superclasses can be defined inthree ways. Most commonly, the superclasses are listed in thecontains= argument in the call tosetClassthat creates the subclass. In this case the subclass willcontain all the slots of the superclass, and the relationbetween the class is calledsimple, as it in fact is.Superclasses can also be definedexplicitly by a call tosetIs; in this case, therelation requires methods to be specified to go from subclass tosuperclass. Thirdly, a class union is a superclass of all themembers of the union. In this case too the relation is simple,but notice that the relation is defined when the superclass iscreated, not when the subclass is created as with thecontains= mechanism.
The definition of a superclass will also potentially containits own direct superclasses. These are considered (and shown) assuperclasses at distance 2 from the original class; their directsuperclasses are at distance 3, and so on. All these arelegitimate superclasses for purposes such as method selection.
When superclasses are defined by including the names ofsuperclasses in thecontains= argument tosetClass, an object from the class will have all theslots defined for its own classand all the slots definedfor all its superclasses as well.
The information about the relation between a class and aparticular superclass is encoded as an object of classSClassExtension. A list of such objects forthe superclasses (and sometimes for the subclasses) is included inthe metadata object defining the class. If you need to computewith these objects (for example, to compare the distances), callthe functionextends with argumentfullInfo=TRUE.
The objects from a class created by a call toneware defined by theprototype object for the class and byadditional arguments in the call tonew, which arepassed to a method for that class for the functioninitialize.
Each class representation object contains a prototype objectfor the class (although for a virtual class the prototype may beNULL). The prototype object must have values for all theslots of the class.By default, these are the prototypes of the corresponding slotclasses. However, the definition of the class can specify anyvalid object for any of the slots.
There are a number of ‘basic’ classes, corresponding to theordinary kinds of data occurring inR. For example,"numeric" is a class corresponding to numeric vectors.The other vector basic classes are"logical","integer","complex","character","raw","list"and"expression".The prototypes forthe vector classes are vectors of length 0 of the correspondingtype. Notice that basic classes are unusual in that theprototype object is from the class itself.
In addition to the vector classes there are also basic classescorresponding to objects in thelanguage, such as"function" and"call".These classes are subclasses of the virtual class"language".Finally, there are object types and corresponding basic classes for“abnormal” objects, such as"environment" and"externalptr".These objects do not follow thefunctional behavior of the language; in particular, they are notcopied and so cannot have attributes or slots defined locally.
All these classes can be used as slots or assuperclasses for any other class definitions, although they donot themselves come with an explicit class. For the abnormalobject types, a special mechanism is used to enable inheritanceas described below.
A class definition can extend classes other thanregular S4 classes, usually by specifying them in thecontains= argument tosetClass. Three groupsof such classes behave distinctly:
S3 classes, which must have been registered by a previous call tosetOldClass (you can check that this has been doneby callinggetClass, which should return a class thatextendsoldClass);
One of theR object types, typically a vector type, which thendefines the type of the S4 objects, but also a type such asenvironment that can not be used directly as a typefor an S4 object. Seebelow.
One of the pseudo-classesmatrixandarray, implying objects witharbitrary vector types plus thedim anddimnamesattributes.
This section describes the approach to combining S4 computationswith older S3 computations by using such classes as superclasses. Thedesign goal is to allow the S4 class to inherit S3 methods anddefault computations in as consistent a form as possible.
As part of a general effort to make the S4 and S3 code in R moreconsistent, when objects from an S4 class are used as the firstargument to a non-default S3 method, either for an S3 generic function(one that callsUseMethod) or for one of the primitivefunctions that dispatches S3 methods, an effort is made to provide avalid object for that method. In particular, if the S4 class extendsan S3 class ormatrix orarray, and there is an S3method matching one of these classes, the S4 object will be coerced toa valid S3 object, to the extent that is possible given that there isno formal definition of an S3 class.
For example, suppose"myFrame" is an S4 class that includes theS3 class"data.frame" in thecontains= argument tosetClass. If an object from this S4 class is passed toa function, sayas.matrix, that has an S3 method for"data.frame", the internal code forUseMethodwill convert the object to a data frame; in particular, to an S3object whose class attribute will be the vector corresponding to theS3 class (possibly containing multiple class names). Similarly for anS4 object inheriting from"matrix" or"array", the S4object will be converted to a valid S3 matrix or array.
Note that the conversion isnot applied when an S4 object ispassed to the default S3 method. Some S3 generics attempt to dealwith general objects, including S4 objects. Also, no transformationis applied to S4 objects that do not correspond to a selected S3method; in particular, to objects from a class that does not containeither an S3 class or one of the basic types. SeeasS4for the transformation details.
In addition to explicit S3 generic functions, S3 methods aredefined for a variety of operators and functions implemented asprimitives. These methods are dispatched by some internal Ccode that operates partly through the same code as real S3generic functions and partly via special considerations (forexample, both arguments to a binary operator are examined whenlooking for methods). The same mechanism for adapting S4objects to S3 methods has been applied to these computations aswell, with a few exceptions such as generating an error if an S4object that does not extend an appropriate S3 class or type ispassed to a binary operator.
The remainder of this section discusses the mechanisms forinheriting from basic object types. Seematrixorarrayfor inhering from the matrix and arraypseudo-classes, or from time-series. For thecorresponding details for inheritancefrom S3 classes, seesetOldClass.
An object from a class that directly and simply contains oneof the basic object types inR, has implicitly a corresponding.Data slot of that type, allowing computations to extractor replace the data part while leaving other slotsunchanged. If the type is one that can accept attributes and isduplicated normally, the inheritance also determines the type of theobject; if the class definition has a.Data slotcorresponding to a normal type, the class of theslot determines the type of the object (that is, the value oftypeof(x)).For such classes,.Data is a pseudo-slot; thatis, extracting or setting it modifies the non-slot data in theobject. The functionsgetDataPart andsetDataPart are a cleaner, but essentiallyequivalent way to deal with the data part.
Extending a basic type this way allows objects touse old-style code for the corresponding type as well as S4methods. Any basic type can be used for.Data, buta few types are treated differently because they do not behave like ordinary objects;for example,"NULL", environments, and external pointers.Classes extend these types by having a slot,.xData,itself inherited from an internally defined S4 class. Thisslot actually contains an object of the inherited type, toprotect computations from the reference semantics of the type.Coercing to the nonstandard object type then requires anactual computation, rather than the"simple" inclusionfor other types and classes. The intent is that programmerswill not need to take account of the mechanism, but oneimplication is that you shouldnot explicitly use thetype of an S4 object to detect inheritance from an arbitraryobject type. Useis and similar functions instead.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Methods_Details for analogous discussion of methods,setClass for details of specifying class definitions,is,as,new,slot
Given a vector of class names or a list of class definitions, thefunction returns an adjacency matrix of the superclasses of theseclasses; that is, a matrix with class names as the row and columnnames and with element [i, j] being 1 if the class in column j is adirect superclass of the class in row i, and 0 otherwise.
The matrix has the information implied by thecontains slot ofthe class definitions, but in a form that is often more convenient forfurther analysis; for example, an adjacency matrix is used in packagesand other software to construct graph representations of relationships.
classesToAM(classes, includeSubclasses = FALSE, abbreviate = 2)classesToAM(classes, includeSubclasses=FALSE, abbreviate=2)
classes | Either a character vector of class names or a list, whoseelements can be either class names or class definitions. Thelist is convenient, for example, to include the package slot forthe class name. See the examples. |
includeSubclasses | A logical flag; if |
abbreviate | Control of the abbreviation of the row and/or column labels ofthe matrix returned: values 0, 1, 2, or 3 abbreviate neither,rows, columns or both. The default, 2, is useful for printingthe matrix, since class names tend to be more than onecharacter long, making for spread-out printing. Values of 0or 3 would be appropriate for making a graph (3 avoids thetendency of some graph plotting software to produce labels inminuscule font size). |
For each of the classes, the calculation gets all the superclassnames from the class definition, and finds the edges in those classes'definitions; that is, all the superclasses at distance 1. Thecorresponding elements of the adjacency matrix are set to 1.
The adjacency matrices for the individual class definitions aremerged. Note two possible kinds of inconsistency, neither of whichshould cause problems except possibly with identically named classes fromdifferent packages. Edges are computed from each superclassdefinition, so that information overrides a possible inference fromextension elements with distance > 1 (and it should). Whenmatrices from successive classes in the argument are merged, thecomputations do not currently check for inconsistencies—this isthe area where possible multiple classes with the same name couldcause confusion. A later revision may include consistency checks.
As described, a matrix with entries 0 or 1, non-zero valuesindicating that the class corresponding to the column is a directsuperclass of the class corresponding to the row. The row andcolumn names are the class names (without package slot).
extends andclassRepresentation for the underlying information from the classdefinition.
## the super- and subclasses of "standardGeneric"## and "derivedDefaultMethod"am <- classesToAM(list(class(show), class(getMethod(show))), TRUE)am## Not run: ## the following function depends on the Bioconductor package RgraphvizplotInheritance <- function(classes, subclasses = FALSE, ...) { if(!require("Rgraphviz", quietly=TRUE)) stop("Only implemented if Rgraphviz is available") mm <- classesToAM(classes, subclasses) classes <- rownames(mm); rownames(mm) <- colnames(mm) graph <- new("graphAM", mm, "directed", ...) plot(graph) cat("Key:\n", paste(abbreviate(classes), " = ", classes, ", ", sep = ""), sep = "", fill = TRUE) invisible(graph)}## The plot of the class inheritance of the package "graph"require(graph)plotInheritance(getClasses("package:graph"))## End(Not run)## the super- and subclasses of "standardGeneric"## and "derivedDefaultMethod"am<- classesToAM(list(class(show), class(getMethod(show))),TRUE)am## Not run:## the following function depends on the Bioconductor package RgraphvizplotInheritance<-function(classes, subclasses=FALSE,...){if(!require("Rgraphviz", quietly=TRUE)) stop("Only implemented if Rgraphviz is available") mm<- classesToAM(classes, subclasses) classes<- rownames(mm); rownames(mm)<- colnames(mm) graph<- new("graphAM", mm,"directed",...) plot(graph) cat("Key:\n", paste(abbreviate(classes)," = ", classes,", ", sep=""), sep="", fill=TRUE) invisible(graph)}## The plot of the class inheritance of the package "graph"require(graph)plotInheritance(getClasses("package:graph"))## End(Not run)
The functionclassName() generates avalid references to a class, including the name of the packagecontaining the class definition. The object returned, from class"className", is theunambiguous way to refer to a class, for example when callingsetMethod, just in case multiple definitions of theclass exist.
Function"multipleClasses" returns information about multipledefinitions of classes with thesame name from different packages.
className(class, package)multipleClasses(details = FALSE)className(class, package)multipleClasses(details=FALSE)
class,package | The character string name of a class and, optionally, of the packageto which it belongs. If argument If there is no package argument or slot, adefinition for the class must exist and will be used to define thepackage. If there are multiple definitions, one will be chosen and awarning printed giving the other possibilities. |
details | If If |
The table of class definitions used internally can maintain multipledefinitions for classes with the same name but coming from differentpackages.If identical class definitions are encountered, only one classdefinition is kept; this occurs most often with S3 classes that havebeen specified in calls tosetOldClass. For trueclasses, multiple class definitions are unavoidable in general if twopackages happen to have used the same name, independently.
Overriding a class definition in another package with the same name deliberately is usually a badidea.AlthoughR attempts to keep and use the two definitions (as ofversion 2.14.0), ambiguities are always possible. It is moresensible to define a new class that extends an existing class but hasa different name.
A call toclassName() returns an object from class"className".
A call tomultipleClasses() returns either a charactervector or a named list of class definitions. In either case, testingthe length of the returned value for being greater than0 is acheck for the existence of multiply defined classes.
The class"className" extends"character" and has a slot"package", also of class"character".
## Not run: className("vector") # will be found, from package "methods"className("vector", "magic") # OK, even though the class doesn't existclassName("An unknown class") # Will cause an error## End(Not run)## Not run:className("vector")# will be found, from package "methods"className("vector","magic")# OK, even though the class doesn't existclassName("An unknown class")# Will cause an error## End(Not run)
These are the objects that hold the definition ofclasses of objects. They are constructed and stored as meta-data bycalls to the functionsetClass. Don't manipulate themdirectly, except perhaps to look at individual slots.
Class definitions are stored as metadata in various packages.Additional metadata supplies information on inheritance (the result ofcalls tosetIs). Inheritance information implied by theclass definition itself (because the class contains one or more otherclasses) is also constructed automatically.
When a class is to be used in an R session, this information isassembled to complete the class definition. The completion is asecond object of class"classRepresentation", cached for thesession or until something happens to change the information. A calltogetClass returns the completed definition of a class;a call togetClassDef returns the stored definition(uncompleted).
In particular, completion fills in the upward- and downward-pointinginheritance information for the class, in slotscontains andsubclasses respectively. It's in principle important to notethat this information can depend on which packages are installed,since these may define additional subclasses or superclasses.
slots:A named list of the slots in this class; theelements of the list are the classes to which the slots mustbelong (or extend), and the names of the list gives thecorresponding slot names.
contains:A named list of the classes this class‘contains’; the elements of the list are objects ofSClassExtension. The list may be only thedirect extensions or all the currently known extensions (see thedetails).
virtual:Logical flag, set toTRUE if this isa virtual class.
prototype:The object that represents the standardprototype for this class; i.e., the data and slots returned by acall tonew for this class with no specialarguments. Don't mess with the prototype object directly.
validity:Optionally, a function to be used to testthe validity of objects from this class.SeevalidObject.
access:Access control information. Not currently used.
className:The character string name of the class.
package:The character string name of the package towhich the class belongs. Nearly always the package on which themetadata for the class is stored, but in operations such asconstructing inheritance information, the internal package namerules.
subclasses:A named list of the classes known toextend this class'; the elements of the list are objects of classSClassExtension. The list is currently onlyfilled in when completing the class definition (see the details).
versionKey:Object of class"externalptr";eventually will perhaps hold some versioning information, but notcurrently used.
sealed:Object of class"logical"; is thisclass sealed? If so, no modifications are allowed.
See functionsetClass to supply the information in theclass definition.SeeClasses_Details for a more basic discussion of class information.
Special documentation can be supplied to describe theclasses and methods that are created by the software in the methodspackage. Techniques to access this documentation and to create itin R help files are described here.
You can ask for on-line help for class definitions, for specificmethods for a generic function, and for general discussion ofmethods for a generic function. These requests use the?operator (seehelp for a general description ofthe operator). Of course, you are at the mercy of the implementeras to whether thereis any documentation on the correspondingtopics.
Documentation on a class uses the argumentclass on the leftof the?, and the name of the class on the right; forexample,
class ? genericFunction
to ask for documentation on the class"genericFunction".
When you want documentation for the methods defined for a particularfunction, you can ask either for a general discussion of the methodsor for documentation of a particular method (that is, the method thatwould be selected for a particular set of actual arguments).
Overall methods documentation is requested bycalling the? operator withmethods as the left-sideargument and the name of the function as the right-side argument. Forexample,
methods ? initialize
asks for documentation on the methods for theinitializefunction.
Asking for documentation on a particular method is done by giving afunction call expression as the right-hand argument to the"?"operator. There are two forms, depending on whether you prefer togive the class names for the arguments or expressions that you intendto use in the actual call.
If you planned to evaluate a function call, saymyFun(x, sqrt(wt))and wanted to find out something about the method that would be usedfor this call, put the call on the right of the"?" operator:
?myFun(x, sqrt(wt))
A method will be selected, as it would be for the call itself, anddocumentation for that method will be requested. IfmyFun isnot a generic function, ordinary documentation for the function willbe requested.
If you know the actual classes for which you would like methoddocumentation, you can supply these explicitly in place of theargument expressions. In the example above, if you want methoddocumentation for the first argument having class"maybeNumber"and the second"logical", call the"?" operator, thistime with a left-side argumentmethod, and with a function callon the right using the class names as arguments:
method ? myFun("maybeNumber", "logical")
Once again, a method will be selected, this time corresponding to thespecified classes, and method documentation will be requested. Thisversion only works with generic functions.
The two forms each have advantages. The version with actual argumentsdoesn't require you to figure out (or guess at) the classes of thearguments.On the other hand, evaluating the arguments may take some time,depending on the example.The version with class names does require you to pick classes, butit's otherwise unambiguous. It has a subtler advantage, in that theclasses supplied may be virtual classes, in which case no actualargument will have specifically this class. The class"maybeNumber", for example, might be a class union (see theexample forsetClassUnion).
In either form, methods will be selected as they would be in actualcomputation, including use of inheritance and group genericfunctions. SeeselectMethod for the details, since it isthe function used to find the appropriate method.
The on-line documentation for methods and classes uses some extensionsto the R documentation format to implement the requests for class andmethod documentation described above. See the documentWritingR Extensions for the available markup commands (you shouldhave consulted this document already if you are at the stage ofdocumenting your software).
In addition to the specific markup commands to be described, you cancreate an initial, overall file with a skeleton of documentation forthe methods defined for a particular generic function:
promptMethods("myFun")
will create a file, ‘myFun-methods.Rd’ with a skeleton ofdocumentation for the methods defined for functionmyFun.The output frompromptMethods is suitable if you want todescribe all or most of the methods for the function in one file,separate from the documentation of the generic function itself.Once the file has been filled in and moved to the ‘man’subdirectory of your source package, requests for methodsdocumentation will use that file, both for specific methodsdocumentation as described above, and for overall documentationrequested by
methods ? myFun
You are not required to usepromptMethods, and if you do, youmay not want to use the entire file created:
If you want to document the methods in the file containing thedocumentation for the generic function itself, you cancut-and-paste to move the\alias lines and theMethods section from the file created bypromptMethods to the existing file.
On the other hand, if these are auxiliary methods, and you onlywant to document the added or modified software, you should stripout all but the relevant\alias lines for the methods ofinterest, and remove all but the corresponding\itementries in theMethods section. Note that in this case youwill usually remove the first\alias line as well, sincethat is the marker for general methods documentation on thisfunction (in the example, ‘\alias{myfun-methods}’).
If you simply want to direct documentation for one or more methods toa particular R documentation file, insert the appropriate alias.
... in Method SignaturesThe “...” argument inR functions is treated specially, in that itmatches zero, one or more actual arguments (and so, objects). Amechanism has been added toR to allow “...” as the signature of ageneric function. Methods defined for such functions will beselected and called whenall the arguments matching “...”are from the specified class or from some subclass of that class.
Beginning with version 2.8.0 ofR, S4 methods can be dispatched(selected and called) corresponding to the special argument “...”.Currently, “...” cannot be mixed with other formal arguments:either the signature of the generic function is “...” only, or itdoes not contain “...”. (This restriction may be lifted in a futureversion.)
Given a suitable generic function, methods are specified in theusual way by a call tosetMethod. The methoddefinition must be written expecting all the arguments correspondingto “...” to be from the class specified in the method's signature,or from a class that extends that class (i.e., a subclass of thatclass).
Typically the methods will pass “...” down to another function orwill create a list of the arguments and iterate over that. See theexamples below.
When you have a computation that is suitable for more than one existingclass, a convenient approach may be to define a union of theseclasses by a call tosetClassUnion. See the examplebelow.
SeeMethods_Details for a general discussion. The following assumesyou have read the “Method Selection and Dispatch” section ofthat documentation.
A method selecting on “...” is specified by a single class in thecall tosetMethod. If all the actual argumentscorresponding to “...” have this class, the corresponding method isselected directly.
Otherwise, the class of each argument and that class' superclasses arecomputed, beginning with the first “...” argument. For the firstargument, eligible methods are those for any of the classes. Foreach succeeding argument that introduces a class not considered previously, the eligible methods are furtherrestricted to those matching the argument's class orsuperclasses. If no further eligible classes exist, the iterationbreaks out and the default method, if any, is selected.
At the end of the iteration, one or more methods may be eligible.If more than one, the selection looks for the method with the leastdistance to the actual arguments. For each argument, any inheritedmethod corresponds to a distance, available from thecontainsslot of the class definition. Since the same class can arise formore than one argument, there may be several distances associatedwith it. Combining them is inevitably arbitrary: the currentcomputation uses the minimum distance. Thus, for example, if amethod matched one argument directly, one as first generationsuperclass and another as a second generation superclass, thedistances are 0, 1 and 2. The current selection computation woulduse distance 0 for thismethod. In particular, this selection criterion tends to use a method thatmatches exactly one or more of the arguments' class.
As with ordinary method selection, there may be multiple methodswith the same distance. A warning message is issued and one of themethods is chosen (the first encountered, which in this case israther arbitrary).
Notice that, while the computation examines all arguments, theessential cost of dispatch goes up with the number ofdistinct classes among the arguments, likely to be muchsmaller than the number of arguments when the latter is large.
Methods dispatching on “...” were introduced in version 2.8.0 ofR. The initial implementation of the corresponding selection anddispatch is in an R function, for flexibility while the newmechanism is being studied. In this implementation, a local versionofstandardGeneric is inserted in the generic function'senvironment. The local version selects a method according to thecriteria above and calls that method, from the environment of thegeneric function. This is slightly different from the action takenby the C implementation when “...” is not involved. Aside from theextra computing time required, the method is evaluated in a truefunction call, as opposed to the special context constructed by theC version (which cannot be exactly replicated in R code.) However,situations in which different computational results wouldbe obtained have not been encountered so far, and seem veryunlikely.
Methods dispatching on arguments other than “...” arecached by storingthe inherited method in the table of all methods, where it will befound on the next selection with the same combination of classesin the actual arguments (but not used for inheritance searches).Methods based on “...” are also cached, but not found quiteas immediately. As noted, the selected method depends only on theset of classes that occur in the “...” arguments. Each ofthese classes can appear one or more times, so many combinations ofactual argument classes will give rise to the same effectivesignature. The selection computation first computes and sorts thedistinct classes encountered. This gives a label that will becached in the table of all methods, avoiding any further search forinherited classes after the first occurrence. A call toshowMethods will expose such inherited methods.
The intention is that the “...” features will be added to thestandard C code when enough experience with them has been obtained.It is possible that at the same time, combinations of “...” withother arguments in signatures may be supported.
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (For the R version.)
Chambers, John M. (1998)Programming with DataSpringer (For the original S4 version.)
For the general discussion of methods, seeMethods_Details and linksfrom there.
cc <- function(...)c(...)setGeneric("cc")setMethod("cc", "character", function(...)paste(...))setClassUnion("Number", c("numeric", "complex"))setMethod("cc", "Number", function(...) sum(...))setClass("cdate", contains = "character", slots = c(date = "Date"))setClass("vdate", contains = "vector", slots = c(date = "Date"))cd1 <- new("cdate", "abcdef", date = Sys.Date())cd2 <- new("vdate", "abcdef", date = Sys.Date())stopifnot(identical(cc(letters, character(), cd1), paste(letters, character(), cd1))) # the "character" methodstopifnot(identical(cc(letters, character(), cd2), c(letters, character(), cd2)))# the default, because "vdate" doesn't extend "character"stopifnot(identical(cc(1:10, 1+1i), sum(1:10, 1+1i))) # the "Number" methodstopifnot(identical(cc(1:10, 1+1i, TRUE), c(1:10, 1+1i, TRUE))) # the defaultstopifnot(identical(cc(), c())) # no arguments implies the default methodsetGeneric("numMax", function(...)standardGeneric("numMax"))setMethod("numMax", "numeric", function(...)max(...))# won't work for complex datasetMethod("numMax", "Number", function(...) paste(...))# should not be selected w/o complex argsstopifnot(identical(numMax(1:10, pi, 1+1i), paste(1:10, pi, 1+1i)))stopifnot(identical(numMax(1:10, pi, 1), max(1:10, pi, 1)))try(numMax(1:10, pi, TRUE)) # should be an error: no default method## A generic version of paste(), dispatching on the "..." argument:setGeneric("paste", signature = "...")setMethod("paste", "Number", function(..., sep, collapse) c(...))stopifnot(identical(paste(1:10, pi, 1), c(1:10, pi, 1)))cc<-function(...)c(...)setGeneric("cc")setMethod("cc","character",function(...)paste(...))setClassUnion("Number", c("numeric","complex"))setMethod("cc","Number",function(...) sum(...))setClass("cdate", contains="character", slots= c(date="Date"))setClass("vdate", contains="vector", slots= c(date="Date"))cd1<- new("cdate","abcdef", date= Sys.Date())cd2<- new("vdate","abcdef", date= Sys.Date())stopifnot(identical(cc(letters, character(), cd1), paste(letters, character(), cd1)))# the "character" methodstopifnot(identical(cc(letters, character(), cd2), c(letters, character(), cd2)))# the default, because "vdate" doesn't extend "character"stopifnot(identical(cc(1:10,1+1i), sum(1:10,1+1i)))# the "Number" methodstopifnot(identical(cc(1:10,1+1i,TRUE), c(1:10,1+1i,TRUE)))# the defaultstopifnot(identical(cc(), c()))# no arguments implies the default methodsetGeneric("numMax",function(...)standardGeneric("numMax"))setMethod("numMax","numeric",function(...)max(...))# won't work for complex datasetMethod("numMax","Number",function(...) paste(...))# should not be selected w/o complex argsstopifnot(identical(numMax(1:10, pi,1+1i), paste(1:10, pi,1+1i)))stopifnot(identical(numMax(1:10, pi,1), max(1:10, pi,1)))try(numMax(1:10, pi,TRUE))# should be an error: no default method## A generic version of paste(), dispatching on the "..." argument:setGeneric("paste", signature="...")setMethod("paste","Number",function(..., sep, collapse) c(...))stopifnot(identical(paste(1:10, pi,1), c(1:10, pi,1)))
"environment"A formal class for R environments.
Objects can be created by calls of the formnew("environment", ...).The arguments in ..., if any, should be named and will be assigned tothe newly created environment.
signature(from = "ANY", to = "environment"):callsas.environment.
signature(object = "environment"):Implements the assignments in the new environment. Note that theobject argument is ignored; a new environment isalways created, since environments are not protected by copying.
"envRefClass"Support Class to Implement R Objects using Reference Semantics
The software described here is an initial version. The eventual goalis to support reference-style classes with software inR itselfor using inter-system interfaces. The current implementation (Rversion 2.12.0) is preliminary and subject to change, and currentlyincludes only theR-only implementation. Developers are encouragedto experiment with the software, but the description here is more thanusually subject to change.
This class implements basic reference-style semantics forRobjects. Objects normally do not come directly from this class, butfrom subclasses defined by a call tosetRefClass.The documentation below is technical background describing the implementation, but applicationsshould use the interface documented undersetRefClass,in particular the$ operator and field accessor functions asdescribed there.
The design of reference classes forR divides those classes upaccording to the mechanism used for implementing references, fields,and class methods.Each version of this mechanism is defined by abasic referenceclass, which must implement a set of methods and provide somefurther information used bysetRefClass.
The required methods are for operators$ and$<- toget and set a field in an object, and forinitialize toinitialize objects.
To support these methods, the basic reference class needs to have someimplementation mechanism to store and retrieve data from fields in theobject.The mechanism needs to be consistent with reference semantics; thatis, changes made to the contents of an object are global, seen by anycode accessing that object, rather than only local to the functioncall where the change takes place.As described below, classenvRefClass implements referencesemantics through specialized use ofenvironmentobjects.Other basic reference classes may use an interface to a language suchas Java or C++ using reference semantics for classes.
Usually, theR user will be able to invoke class methods on theclass, using the$ operator. The basic reference classmethod for$ needs to make this possible. Essentially, theoperator must return anR function corresponding to the object andthe class method name.
Class methods may include an implementation of data abstraction, inthe sense that fields are accessed by “get” and “set”methods. The basic reference class provides this facility by settingthe"fieldAccessorGenerator" slot in its definition to afunction of one variable.This function will be called bysetRefClass with thevector of field names as arguments.The generator function must return a list of defined accessorfunctions.An element corresponding to a get operation is invoked with noarguments and should extract the corresponding field; an element for aset operation will be invoked with a single argument, the value to beassigned to the field.The implementation needs to supply the object, since that is not anargument in the method invocation.The mechanism used currently byenvRefClass is described below.
Two virtual classes are supplied to test for reference objects:is(x, "refClass") tests whetherx comes from a classdefined using the reference class mechanism described here;is(x, "refObject") tests whether the object has referencesemantics generally, including the previous classes and also classesinheriting from theR types with reference semantics, such as"environment".
Installed class methods are"classMethodDefinition" objects,with slots that identify the name of the function as a class methodand the other class methods called from this method.The latter information is determined heuristically when the class isdefined by using thecodetools recommended package. Thispackage must be installed when reference classes are defined, but isnot needed in order to use existing reference classes.
John Chambers
Definitions of functions and/or methods from a source file areinserted into a package, using thetrace mechanism.Typically, this allows testing or debugging modified versions of a fewfunctions without reinstalling a large package.
evalSource(source, package = "", lock = TRUE, cache = FALSE)insertSource(source, package = "", functions = , methods = , force = )evalSource(source, package="", lock=TRUE, cache=FALSE)insertSource(source, package="", functions=, methods=, force=)
source | A file to be parsed and evaluated by The argument to |
package | Optionally, the name of the package to which the new code correspondsand into which it will beinserted. Although the computations will attempt to infer the packageif it is omitted, the safe approach is to supply it. In the case of apackage that is not attached to the search list, the package name mustbe supplied. |
functions,methods | Optionally, the character-string names of the functions to beused in the insertion. Names supplied in the If |
lock,cache | Optional arguments to control the actions taken by The default settings are generally recommended, the |
force | If |
Thesource file is parsed and evaluated, suppressing by defaultthe actual caching of method and class definitions contained in it, sothat functions and methods can be tested out in a reversible way.The result, if all goes well, is an environment containing theassigned objects and metadata corresponding to method and class definitionsin the source file.
From this environment, the objects are inserted into the package, intoits namespace if it has one, for use during the current session oruntil reverting to the original version by a call tountrace.The insertion is done by calls to the internal version oftrace, to make reversion possible.
Because the trace mechanism is used, only function-type objects willbe inserted, functions themselves or S4 methods.
When thefunctions andmethods arguments are bothomitted,insertSource selects all suitable objects from theresult of evaluating thesource file.
In all cases,only objects in the source file that differ fromthe corresponding objects in the package are inserted.The definition of “differ” is that either the argument list(including default expressions) or the body of the function is notidentical.Note that in the case of a method, there need be no specific methodfor the corresponding signature in the package: the comparison is madeto the method that would be selected for that signature.
Nothing in the computation requires that the source file supplied bethe same file as in the original package source, although that case isboth likely and sensible if one is revising the package. Nothing inthe computations compares source files: the objects generated byevaluatingsource are compared as objects to the content of the package.
An object from class"sourceEnvironment", a subclass of"environment" (see the section on the class)The environment contains the versionsofall object resulting from evaluation of the source file.The class also has slots for the time of creation, the source fileand the package name.Future extensions may use these objects for versioning or other code tools.
The object returned can be used in debugging (see the section on thattopic) or as thesourceargument in a future call toinsertSource. If only some of therevised functions were inserted in the first call, others can beinserted in a later call without re-evaluating the source file, bysupplying the environment and optionally suitablefunctionsand/ormethods argument.
Once a function or method has been inserted into a package byinsertSource, it can be studied by the standard debugging tools;for example,debug or the various versions oftrace.
Calls totrace should take the extra argumentedit= env, whereenv is the value returned by the call toevalSource.The trace mechanism has been used to install the revised version fromthe source file, and supplying the argument ensures that it is thisversion, not the original, that will be traced. See the examplebelow.
To turn tracing off, but retain the source version, usetrace(x,edit = env) as in the example. To return to the original versionfrom the package, useuntrace(x).
"sourceEnvironment"Objects from this class can be treated as environments, to extract theversion of functions and methods generated byevalSource.The objects also have the following slots:
packageName:The character-string name of the packageto which the source code corresponds.
dateCreated: The date and time that the source file wasevaluated (usually from a call toSys.time).
sourceFile:The character-string name of the source fileused.
Note that using the environment does not change thedateCreated.
trace for the underlying mechanism, and also for theedit= argument that can be used for somewhat similar purposes;that function and alsodebug andsetBreakpoint, for techniques more oriented totraditional debugging styles.The present function is directly intended for the case that one ismodifying some of the source for an existing package, although it canbe used as well by inserting debugging code in the source (more usefulif the debugging involved is non-trivial). As noted in the detailssection, the sourcefile need not be the same one in the original package source.
## Not run: ## Suppose package P0 has a source file "all.R"## First, evaluate the source, and from it## insert the revised version of methods for summary() env <- insertSource("./P0/R/all.R", package = "P0", methods = "summary")## now test one of the methods, tracing the version from the source trace("summary", signature = "myMat", browser, edit = env)## After testing, remove the browser() call but keep the source trace("summary", signature = "myMat", edit = env)## Now insert all the (other) revised functions and methods## without re-evaluating the source file.## The package name is included in the object env. insertSource(env)## End(Not run)## Not run:## Suppose package P0 has a source file "all.R"## First, evaluate the source, and from it## insert the revised version of methods for summary() env<- insertSource("./P0/R/all.R", package="P0", methods="summary")## now test one of the methods, tracing the version from the source trace("summary", signature="myMat", browser, edit= env)## After testing, remove the browser() call but keep the source trace("summary", signature="myMat", edit= env)## Now insert all the (other) revised functions and methods## without re-evaluating the source file.## The package name is included in the object env. insertSource(env)## End(Not run)
Functions to find classes:isClass tests for a class;findClass returns the name(s) of packages containing theclass;getClasses returns the names of all the classes in anenvironment, typically a namespace. To examine the definition of a class, usegetClass.
isClass(Class, formal=TRUE, where)getClasses(where, inherits = missing(where))findClass(Class, where, unique = "")## The remaining functions are retained for compatibility## but not generally recommendedremoveClass(Class, where)resetClass(Class, classDef, where)sealClass(Class, where)isClass(Class, formal=TRUE, where)getClasses(where, inherits= missing(where))findClass(Class, where, unique="")## The remaining functions are retained for compatibility## but not generally recommendedremoveClass(Class, where)resetClass(Class, classDef, where)sealClass(Class, where)
Class | character string name for the class. The functions willusually take a class definition instead of the string. To restrictthe class to those defined in a particular package, set the |
where | the To restrict the search to classes in a particular package, use |
formal |
|
unique | if |
inherits | in a call to |
classDef | For |
isClass:Is this the name of a formally defined class?
getClasses:The names of all the classes formally defined onwhere. Ifcalled with no argument, all the classes visible from thecalling function (if called from the top-level, all the classesin any of the environments on the search list). Thewhere argument is used to search only in a particular package.
findClass:The list of environments inwhich a class definition ofClass is found. Ifwhere is supplied, a list is still returned, either emptyor containing the environment corresponding towhere.By default when called from theR session, the globalenvironment and all the currentlyattached packages are searched.
Ifunique is supplied as a character string,findClass will warn if there is more than one definitionvisible (using the string to identify the purpose of the call),and will generate an error if no definition can be found.
The remaining functions are retained forback-compatibility and internal use, but not generally recommended.
removeClass:Remove the definition of this class. This can't be used if theclass is in another package, and would rarely be needed insource code defining classes in a package.
resetClass:Reset the internal definition of a class. Not legitimate for aclass definition not in this package and rarely needed otherwise.
sealClass:Seal the current definition of the specifiedclass, to prevent further changes, by setting the correspondingslot in the class definition. This is rarely used, sinceclasses in loaded packages are sealed by locking their namespace.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (Chapter 9 has some details not in the later reference.)
getClass,Classes_Details,Methods_Details,makeClassRepresentation
The functionfindMethods converts the methods defined in a table for a genericfunction (as used for selection of methods) into a list, for study ordisplay. The list is actually from the classlistOfMethods(see the section describing the class, below).
The list will be limitedto the methods defined in environmentwhere if that argument issupplied and limited to those including one or more of thespecifiedclasses in the method signature if that argument issupplied.
To see the actual table (anenvironment) usedfor methods dispatch, callgetMethodsForDispatch.The names of the list returned byfindMethods are the names ofthe objects in the table.
The functionfindMethodSignatures returns a character matrixwhose rows are the class names from the signature of the correspondingmethods; it operates either from a list returned byfindMethods, or by computing such a list itself, given the samearguments asfindMethods .
The functionhasMethods returnsTRUE orFALSEaccording to whether there is a non-empty table of methods forfunctionf in the environment or search positionwhere(or for the generic function generally ifwhere is missing).
The deprecated functiongetMethods (defunct for defaulttable=FALSE)is an older alternative tofindMethods , returning information in the form of an object ofclassMethodsList, previously used for method dispatch. Thisclass of objects is deprecated generally and will disappear in afuture version of R.
findMethods(f, where, classes = character(), inherited = FALSE, package = "")findMethodSignatures(..., target = TRUE, methods = )hasMethods(f, where, package)## Deprecated in 2010 and defunct in 2015 for 'table = FALSE':getMethods(f, where, table = FALSE) # deprecated -> use getMethodsForDispatch()findMethods(f, where, classes= character(), inherited=FALSE, package="")findMethodSignatures(..., target=TRUE, methods=)hasMethods(f, where, package)## Deprecated in 2010 and defunct in 2015 for 'table = FALSE':getMethods(f, where, table=FALSE)# deprecated -> use getMethodsForDispatch()
f | A generic function or the character-string name of one. |
where | Optionally, an environment or position on the search listto look for methods metadata. If |
table | If |
classes | If supplied, only methods whose signatures contain atleast one of the supplied classes will be included in the valuereturned. |
inherited | Logical flag; if |
... | In the call to |
target | Optional flag to |
methods | In the call to |
package | In a call to |
The functions obtain a table of the defined methods, either from thegeneric function or from the stored metadata object in the environmentspecified bywhere. In a call togetMethods, the information in the table is convertedas described above to produce the returned value, except with thetable argument.
Note thathasMethods, but not the other functions, can be usedeven if no generic function of this name is currently found. In thiscasepackage must either be supplied as an argument or includedas an attribute off, since the package name is part of theidentification of the methods tables.
The class"listOfMethods" returns the methods as a named listof method definitions (or a primitive function, see the slotdocumentation below). The namesare the strings used to store the corresponding objects in theenvironment from which method dispatch is computed.The current implementation uses the names of the corresponding classesin the method signature, separated by"#" if more than oneargument is involved in the signature.
.Data:Object of class"list" The methoddefinitions.
Note that these may include the primitive functionitself as default method,when the generic corresponds to a primitive. (Basically, becauseprimitive functions are abnormal R objects, which cannot currently beextended as method definitions.) Computations that use the returnedlist to derive other information need to take account of thispossibility. See the implementation offindMethodSignaturesfor an example.
arguments:Object of class"character". Thenames of the formal arguments in the signature of the generic function.
signatures:Object of class"list". A list ofthe signatures of the individual methods. This is currently theresult of splitting thenames according to the"#"separator.
If the object has been constructed from a table, as when returned byfindMethods, the signatures will all have the same length.However, a list rather than a character matrix is used forgenerality. CallingfindMethodSignatures as in the examplebelow will always convert to the matrix form.
generic:Object of class"genericFunction".The generic function corresponding to these methods. Thereare plans to generalize this slot to allow reference to the function.
names:Object of class"character". Thenames as noted are the class names separated by"#" .
Class"namedList", directly.
Class"list", by class"namedList", distance 2.
Class"vector", by class"namedList", distance 3.
showMethods,selectMethod,Methods_Details
mm <- findMethods("Ops")findMethodSignatures(methods = mm)mm<- findMethods("Ops")findMethodSignatures(methods= mm)
Beginning with R version 1.8.0, the class of an object contains theidentification of the package in which the class is defined. ThefunctionfixPre1.8 fixes and re-assigns objects missing that information(typically because they were loaded from a file saved with a previousversion of R.)
fixPre1.8(names, where)fixPre1.8(names, where)
names | Character vector of the names of all the objects to befixed and re-assigned. |
where | The environment from which to look for the objects, andfor class definitions. Defaults to the top environment of thecall to |
The named object will be saved where it was found. Its classattribute will be changed to the full form required by R 1.8;otherwise, the contents of the object should be unchanged.
Objects will be fixed and re-assigned only if all the followingconditions hold:
The named object exists.
It is from a defined class (not a basic datatype whichhas no actual class attribute).
The object appears to be from an earlier version of R.
The class is currently defined.
The object is consistent with the current class definition.
If any condition except the second fails, a warning message isgenerated.
Note thatfixPre1.8 currently fixesonly the change inclass attributes. In particular, it will not fix binary versions ofpackages installed with earlier versions of R if these useincompatible features. Such packages must be re-installed fromsource, which is the wise approach always when major version changesoccur in R.
The names of all the objects that were in fact re-assigned.
Generic functions (objects from or extending classgenericFunction) are extended function objects,containing information used in creating and dispatching methods forthis function. They also identify the package associated with thefunction and its methods.
Generic functions are created and assigned bysetGeneric orsetGroupGeneric and, indirectly, bysetMethod.
As you might expectsetGeneric andsetGroupGeneric create objects of class"genericFunction" and"groupGenericFunction" respectively.
.Data:Object of class"function", thefunction definition of the generic, usually createdautomatically as a call tostandardGeneric.
generic:Object of class"character", thename of the generic function.
package:Object of class"character", thename of the package to which the function definition belongs(andnot necessarily where the generic function isstored). If the package is not specified explicitly in thecall tosetGeneric, it is usually the package on whichthe corresponding non-generic function exists.
group:Object of class"list", the group orgroups to which this generic function belongs. Empty by default.
valueClass:Object of class"character"; ifnot an empty character vector, identifies one or more classes. It isasserted that all methods for this function return objectsfrom these class (or from classes that extend them).
signature:Object of class"character", thevector of formal argument names that can appear in thesignature of methods for this generic function. By default,it is all the formal arguments, except for .... Ordermatters for efficiency: the most commonly used arguments inspecifying methods should come first.
default:Object of class"optionalMethod"(a union of classes"function" and"NULL"), containingthe default method for this function if any. Generatedautomatically and used to initialize method dispatch.
skeleton:Object of class"call", a slot usedinternally in method dispatch. Don't expect to use itdirectly.
Class"function", from data part.
Classes"optionalMethod","PossibleMethod", and"OptionalFunction" by class"function".
Generic function objects are used in the creation and dispatch offormal methods; information from the object is used to create methodslist objects and to merge or update the existing methods for thisgeneric.
The functions documented here manage collections of methods associatedwith a generic function, as well as providing information about thegeneric functions themselves.
isGeneric(f, where, fdef, getName = FALSE)isGroup(f, where, fdef)removeGeneric(f, where)dumpMethod(f, signature, file, where, def)findFunction(f, generic = TRUE, where = topenv(parent.frame()))dumpMethods(f, file, signature, methods, where)signature(...)removeMethods(f, where = topenv(parent.frame()), all = missing(where))setReplaceMethod(f, ..., where = topenv(parent.frame()))getGenerics(where, searchForm = FALSE)isGeneric(f, where, fdef, getName=FALSE)isGroup(f, where, fdef)removeGeneric(f, where)dumpMethod(f, signature, file, where, def)findFunction(f, generic=TRUE, where= topenv(parent.frame()))dumpMethods(f, file, signature, methods, where)signature(...)removeMethods(f, where= topenv(parent.frame()), all= missing(where))setReplaceMethod(f,..., where= topenv(parent.frame()))getGenerics(where, searchForm=FALSE)
f | The character string naming the function. |
where | The environment, namespace, or search-list positionfrom which to search for objects. By default, start at thetop-level environment of the calling function, typically the globalenvironment (i.e., use the search list), or the namespace of apackage from which the call came. It is important to supply thisargument when calling any of these functions indirectly. Withpackage namespaces, the default is likely to be wrong in such calls. |
signature | The class signature of the relevant method. Asignature is a named or unnamed vector of character strings. Ifnamed, the names must be formal argument names for the genericfunction. Signatures are matched to the arguments specified inthe signature slot of the generic function (see the Detailssection of the The |
file | The file or connection on which to dump method definitions. |
def | The function object defining the method; if omitted, thecurrent method definition corresponding to the signature. |
... | Named or unnamed arguments to form a signature. |
generic | In testing or finding functions, should genericfunctions be included. Supply as |
fdef | Optional, the generic function definition. Usually omitted in calls to |
getName | If |
methods | The methods object containing the methods to be dumped. By default,the methods defined for this generic (optionally on the specified |
all | in |
searchForm | In |
isGeneric:Iffdef isNULL, then test if there is a formalgeneric function namedf in the current search path or inthe position specified bywhere.
Iffdef is non-NULL, then test if it is a formalgeneric function, with name matchingf iff is notmissing.
ThegetName argument allows a function to find the namefrom a function definition. If it isTRUE then the name ofthe generic is returned, orFALSE if this is not a genericfunction definition.
The behavior ofisGeneric andgetGeneric forprimitive functions is slightly different. These functions don'texist as formal generic function objects (for efficiency andhistorical reasons), regardless of whether methods have beendefined for them. For a primitive function,isGenerictests whether methods have been defined, whereasgetGeneric returns what the formal generic functionobject would be, even if no methods have been defined.
removeGeneric,removeMethods:Remove all the methods for the generic function of thisname. In addition,removeGeneric removes the functionitself;removeMethods restores the non-generic functionwhich was the default method. If there was no default method,removeMethods leaves a generic function with no methods.
standardGeneric:Dispatches a method from the current function call for the genericfunctionf. It is an error to callstandardGeneric anywhere except in the body of thecorresponding generic function.
Note thatstandardGeneric is a primitive function inthebase packagefor efficiency reasons, but rather documented here where it belongs naturally.
dumpMethod:Dump the method for this generic function and signature.
findFunction:return a list of either the positions on the search list, or thecurrent top-level environment, on which a function objectforname exists. The returned value isalways alist, use the first element to access the first visible versionof the function. See the example.
NOTE: Use this rather thanfind withmode="function", which is not as meaningful, and has a fewsubtle bugs from its use of regular expressions. Also,findFunction works correctly in the code for a packagewhen attaching the package via a call tolibrary.
dumpMethods:Dump all the methods for this generic.
signature:Returns a named list of classes to be matched to arguments of ageneric function.
getGenerics:returns the names of the genericfunctions that have methods defined onwhere; thisargument can be an environment or an index into the searchlist. By default, the whole search list is used.
The methods definitions are stored withpackage qualifiers; for example, methods for function"initialize" might refer to two different functionsof that name, on different packages. The package namescorresponding to the method list object are contained in theslotpackage of the returned object. The form ofthe returned name can be plain (e.g.,"base"), or inthe form used in the search list ("package:base")according to the value ofsearchForm
isGeneric:If thefdef argument is supplied, take this as thedefinition of the generic, and test whether it is really ageneric, withf as the name of the generic. (This argumentis not available in S-Plus.)
removeGeneric:Ifwhere supplied, just remove the version on this elementof the search list; otherwise, removes the first versionencountered.
standardGeneric:Generic functions should usually have a call tostandardGeneric as their entire body. They can, however,do any other computations as well.
The usualsetGeneric (directly or through callingsetMethod) creates a function with a call tostandardGeneric.
dumpMethod:The resulting source file will recreate the method.
findFunction:Ifgeneric isFALSE, ignore generic functions.
dumpMethods:Ifsignature is supplied only the methods matching thisinitial signature are dumped. (This feature is not found inS-Plus: don't use it if you want compatibility.)
signature:The advantage of usingsignature is to provide a check onwhich arguments you meant, as well as clearer documentation inyour method specification. In addition,signature checksthat each of the elements is a single character string.
removeMethods:ReturnsTRUE iff was a generic function,FALSE (silently) otherwise.
If there is a default method, the function will be re-assigned asa simple function with this definition.Otherwise, the generic function remains but with no methods (soany call to it will generate an error). In either case, afollowing call tosetMethod will consistentlyre-establish the same generic function as before.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
getMethod (also forselectMethod),setGeneric,setClass,showMethods
require(stats) # for lm## get the function "myFun" -- throw an error if 0 or > 1 versions visible:findFuncStrict <- function(fName) { allF <- findFunction(fName) if(length(allF) == 0) stop("No versions of ",fName," visible") else if(length(allF) > 1) stop(fName," is ambiguous: ", length(allF), " versions") else get(fName, allF[[1]])}try(findFuncStrict("myFun"))# Error: no versionlm <- function(x) x+1try(findFuncStrict("lm"))# Error: 2 versionsfindFuncStrict("findFuncStrict")# just 1 versionrm(lm)## method dumping ------------------------------------setClass("A", slots = c(a="numeric"))setMethod("plot", "A", function(x,y,...){ cat("A meth\n") })dumpMethod("plot","A", file="")## Not run: setMethod("plot", "A",function (x, y, ...){ cat("AAAAA\n")})## End(Not run)tmp <- tempfile()dumpMethod("plot","A", file=tmp)## now remove, and see if we can parse the dumpstopifnot(removeMethod("plot", "A"))source(tmp)stopifnot(is(getMethod("plot", "A"), "MethodDefinition"))## same with dumpMethods() :setClass("B", contains="A")setMethod("plot", "B", function(x,y,...){ cat("B ...\n") })dumpMethods("plot", file=tmp)stopifnot(removeMethod("plot", "A"), removeMethod("plot", "B"))source(tmp)stopifnot(is(getMethod("plot", "A"), "MethodDefinition"), is(getMethod("plot", "B"), "MethodDefinition"))require(stats)# for lm## get the function "myFun" -- throw an error if 0 or > 1 versions visible:findFuncStrict<-function(fName){ allF<- findFunction(fName)if(length(allF)==0) stop("No versions of ",fName," visible")elseif(length(allF)>1) stop(fName," is ambiguous: ", length(allF)," versions")else get(fName, allF[[1]])}try(findFuncStrict("myFun"))# Error: no versionlm<-function(x) x+1try(findFuncStrict("lm"))# Error: 2 versionsfindFuncStrict("findFuncStrict")# just 1 versionrm(lm)## method dumping ------------------------------------setClass("A", slots= c(a="numeric"))setMethod("plot","A",function(x,y,...){ cat("A meth\n")})dumpMethod("plot","A", file="")## Not run:setMethod("plot","A",function(x, y,...){ cat("AAAAA\n")})## End(Not run)tmp<- tempfile()dumpMethod("plot","A", file=tmp)## now remove, and see if we can parse the dumpstopifnot(removeMethod("plot","A"))source(tmp)stopifnot(is(getMethod("plot","A"),"MethodDefinition"))## same with dumpMethods() :setClass("B", contains="A")setMethod("plot","B",function(x,y,...){ cat("B ...\n")})dumpMethods("plot", file=tmp)stopifnot(removeMethod("plot","A"), removeMethod("plot","B"))source(tmp)stopifnot(is(getMethod("plot","A"),"MethodDefinition"), is(getMethod("plot","B"),"MethodDefinition"))
Get the definition of a class.
getClass (Class, .Force = FALSE, where)getClassDef(Class, where, package, inherits = TRUE)getClass(Class, .Force=FALSE, where)getClassDef(Class, where, package, inherits=TRUE)
Class | the character-string name of the class, often with a |
.Force | if |
where | environment from which to begin the search for the definition; by default,start at the top-level (global) environment and proceed throughthe search list. |
package | the name or environment of the package asserted to hold thedefinition. If it is a non-empty string it is used instead of |
inherits | logical; should the class definition be retrieved fromany enclosing environment and also from the cache? If |
Class definitions are stored in metadata objects in a packagenamespace or other environment where they are defined. Whenpackages are loaded, the class definitions in the package are cached in an internaltable. Therefore, most calls togetClassDef will find theclass in the cache or fail to find it at all, unlessinheritsisFALSE, in which case only the environment(s) defined bypackage orwhere are searched.
The class cache allows for multiple definitions of thesame class name in separate environments, with of course thelimitation that the package attribute or package name must beprovided in the call to
The object defining the class. If the class definition is not found,getClassDef returnsNULL, whilegetClass, whichcallsgetClassDef, either generates an error or, if.Force isTRUE, returns a simple definition for theclass. The latter case is used internally, but is not typicallysensible in user code.
The non-null returned value is an object of classclassRepresentation.
Use functions such assetClass andsetClassUnion to create class definitions.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
classRepresentation,setClass,isClass.
getClass("numeric") ## a built in classcld <- getClass("thisIsAnUndefinedClass", .Force = TRUE)cld ## a NULL prototype## If you are really curious:utils::str(cld)## Whereas these generate errors:try(getClass("thisIsAnUndefinedClass"))try(getClassDef("thisIsAnUndefinedClass"))getClass("numeric")## a built in classcld<- getClass("thisIsAnUndefinedClass", .Force=TRUE)cld## a NULL prototype## If you are really curious:utils::str(cld)## Whereas these generate errors:try(getClass("thisIsAnUndefinedClass"))try(getClassDef("thisIsAnUndefinedClass"))
The functionselectMethod() returns the method thatwould be selected for a call to functionf if the arguments hadclasses as specified bysignature. Failing to find a methodis an error, unless argumentoptional = TRUE, in which caseNULL is returned.
The functionfindMethod() returns a list ofenvironments that contain a method for the specified function and signature; bydefault, these are a subset of the packages in the current searchlist. See section “UsingfindMethod()” for details.
The functiongetMethod() returns the method corresponding to thefunction and signature supplied similarly toselectMethod, butwithout using inheritance or group generics.
The functionshasMethod() andexistsMethod() test whetherselectMethod() orgetMethod(), respectively, finds a matching method.
selectMethod(f, signature, optional = FALSE, useInherited =, mlist = , fdef = , verbose = , doCache = ) findMethod(f, signature, where) getMethod(f, signature = character(), where, optional = FALSE, mlist, fdef) existsMethod(f, signature = character(), where) hasMethod(f, signature = character(), where)selectMethod(f, signature, optional=FALSE, useInherited=, mlist=, fdef=, verbose=, doCache=) findMethod(f, signature, where) getMethod(f, signature= character(), where, optional=FALSE, mlist, fdef) existsMethod(f, signature= character(), where) hasMethod(f, signature= character(), where)
f | a generic function or the character-string name of one. |
signature | the signature of classes to match to the argumentsof |
where | the environment in which to look for themethod(s). By default, if the call comes from the command line, the table of methods defined in the genericfunction itself is used, except for |
optional | if the selection in |
mlist,fdef,useInherited,verbose,doCache | optional argumentsto |
Thesignature argument specifies classes, corresponding toformal arguments of the generic function; to be precise, to thesignature slot of the generic function object. The argumentmay be a vector of strings identifying classes, and may be named ornot. Names, if supplied, match the names of those formal argumentsincluded in the signature of the generic. That signature is normallyall the arguments except .... However, generic functions can bespecified with only a subset of the arguments permitted, or with thesignature taking the arguments in a different order.
It's a good idea to name the arguments in the signature to avoidconfusion, if you're dealing with a generic that does somethingspecial with its signature. In any case, the elements of thesignature are matched to the formal signature by the same rules usedin matching arguments in function calls (seematch.call).
The strings in the signature may be class names,"missing" or"ANY". SeeMethods_Details for the meaning of these in methodselection. Arguments not supplied in the signature implicitlycorrespond to class"ANY"; in particular, giving an emptysignature means to look for the default method.
A call togetMethod returns the method for a particularfunction and signature. The search for the method makes no use ofinheritance.
The functionselectMethod also looks for a method given thefunction and signature, but makes full use of the method dispatchmechanism; i.e., inherited methods and group generics are taken intoaccount just as they would be in dispatching a method for thecorresponding signature, with the one exception that conditionalinheritance is not used. LikegetMethod,selectMethodreturnsNULL or generates an error ifthe method is not found, depending on the argumentoptional.
BothselectMethod andgetMethod will normally use thecurrent version of the generic function in the R session, which has atable of the methods obtained from all the packages loaded in thesession. Optional arguments can cause a search for the generic function from aspecified environment, but this is rarely a useful idea. In contrast,findMethod has a different default and the optionalwhere= argument may be needed. See the section “UsingfindMethod()”.
The functionsexistsMethod andhasMethod returnTRUE orFALSE according to whether a method is found,the first corresponding togetMethod (no inheritance) and thesecond toselectMethod.
The call toselectMethod orgetMethod returns the selected method, ifone is found.(This class extendsfunction, so you can use the resultdirectly as a function if that is what you want.)Otherwise an error is thrown ifoptional isFALSE andNULL is returned ifoptional isTRUE.
The returned method object is aMethodDefinition object,except that the default method for a primitive function is required to be the primitive itself.Note therefore that the only reliable test that the search failed isis.null().
The returned value offindMethod is a list ofenvironments in which a corresponding method was found; that is, atable of methods including the one specified.
findMethod()As its name suggests, this function is intended to behave likefind, which produces a list of the packages on thecurrent search list which have, and have exported, the object named.That's whatfindMethod does also, by default. The“exported” part in this case means that the package's namespacehas anexportMethods directive for this generic function.
An important distinction is that the absence of such a directive doesnot prevent methods from the package from being called once thepackage is loaded. Otherwise, the code in the package could not useun-exported methods.
So, if your question is whether loading packagethisPkg will define amethod for this function and signature, you need to ask that questionabout the namespace of the package:
findMethod(f, signature, where = asNamespace("thisPkg"))
If the package did not export the method, attaching it and callingfindMethod with nowhere argument will not find themethod.
Notice also that the length of the signature must be what thecorresponding package used. IfthisPkg had only methods forone argument, only length-1 signatures will match (no trailing"ANY"), even if another currently loaded package had signatureswith more arguments.
as()The functionsetAs allows packages to define methods forcoercing one class of objects to another class. This works internallyby defining methods for the generic functioncoerce(from,to),which can not be called directly.
TheR evaluator selectsmethods for this purpose using a different form of inheritance. Whilemethods can be inherited for the object being coerced, they cannotinherit for the target class, since the result would not be a validobject from that class.If you want toexamine the selection procedure, you must supply the optional argumentuseInherited = c(TRUE, FALSE) toselectMethod.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (Section 10.6 for some details of method selection.)
Methods_Details for the details of methodselection;GenericFunctions for other functionsmanipulating methods and generic function objects;MethodDefinition for the class that representsmethod definitions.
testFun <- function(x)xsetGeneric("testFun")setMethod("testFun", "numeric", function(x)x+1)hasMethod("testFun", "numeric") # TRUEhasMethod("testFun", "integer") #TRUE, inheritedexistsMethod("testFun", "integer") #FALSEhasMethod("testFun") # TRUE, default methodhasMethod("testFun", "ANY")testFun<-function(x)xsetGeneric("testFun")setMethod("testFun","numeric",function(x)x+1)hasMethod("testFun","numeric")# TRUEhasMethod("testFun","integer")#TRUE, inheritedexistsMethod("testFun","integer")#FALSEhasMethod("testFun")# TRUE, default methodhasMethod("testFun","ANY")
The functions below produce the package associated with a particularenvironment or position on the search list, or of the packagecontaining a particular function. They are primarily used to supportcomputations that need to differentiate objects on multiple packages.
getPackageName(where, create = TRUE)setPackageName(pkg, env)packageSlot(object)packageSlot(object) <- valuegetPackageName(where, create=TRUE)setPackageName(pkg, env)packageSlot(object)packageSlot(object)<- value
where | the environment or position on the search listassociated with the desired package. |
object | object providing a character string name, plus thepackage in which this object is to be found. |
value | the name of the package. |
create | flag, should a package name be created if none can beinferred? If |
pkg,env | make the string in |
Package names are normally installed during loading of the package,by theINSTALL script or by thelibraryfunction. (Currently, the name is stored as the object.packageName but don't trust this for the future.)
getPackageName returns the character-string name of the package(without the extraneous"package:" found in the search list).
packageSlot returns or sets the package name slot (currentlyan attribute, not a formal slot, but this may change someday).
setPackageName can be used to establish a package name in anenvironment that would otherwise not have one. Thisallows you to create classes and/or methods in an arbitraryenvironment, but it is usually preferable to create packages by thestandardR programming tools (package.skeleton, etc.)
## all the following usually return "base"getPackageName(length(search()))getPackageName(baseenv())getPackageName(asNamespace("base"))getPackageName("package:base")## all the following usually return "base"getPackageName(length(search()))getPackageName(baseenv())getPackageName(asNamespace("base"))getPackageName("package:base")
ReturnsTRUE ifname corresponds to an argument in thecall, either a formal argument to the function, or a component of..., andFALSE otherwise.
hasArg(name)hasArg(name)
name | The name of a potential argument, as an unquoted name orcharacter string. |
The expressionhasArg(x), for example, is similar to!missing(x), with two exceptions. First,hasArg will look foran argument namedx in the call ifx is not a formalargument to the calling function, but... is. Second,hasArg never generates an error if given a name as an argument,whereasmissing(x) generates an error ifx is not aformal argument.
AlwaysTRUE orFALSE as described above.
ftest <- function(x1, ...) c(hasArg(x1), hasArg("y2"))ftest(1) ## c(TRUE, FALSE)ftest(1, 2) ## c(TRUE, FALSE)ftest(y2 = 2) ## c(FALSE, TRUE)ftest(y = 2) ## c(FALSE, FALSE) (no partial matching)ftest(y2 = 2, x = 1) ## c(TRUE, TRUE) partial match x1ftest<-function(x1,...) c(hasArg(x1), hasArg("y2"))ftest(1)## c(TRUE, FALSE)ftest(1,2)## c(TRUE, FALSE)ftest(y2=2)## c(FALSE, TRUE)ftest(y=2)## c(FALSE, FALSE) (no partial matching)ftest(y2=2, x=1)## c(TRUE, TRUE) partial match x1
The implicit generic mechanism stores generic versions offunctionsin a table in a package. The package does not want the currentversion of the function to be a generic, however, and retains thenon-generic version.
When a call tosetMethod orsetGeneric creates a generic version for one of thesefunctions, the object in the table is used.This mechanism is only needed if special arguments were used tocreate the generic; e.g., thesignature or thevalueClassoptions.
FunctionimplicitGeneric() returns the implicitgeneric version,setGenericImplicit() turns a generic implicit,prohibitGeneric() prevents your function from being madegeneric, andregisterImplicitGenerics() saves a set of implicitgeneric definitions in the cached table of the current session.
implicitGeneric(name, where, generic)setGenericImplicit(name, where, restore = TRUE)prohibitGeneric(name, where)registerImplicitGenerics(what, where)implicitGeneric(name, where, generic)setGenericImplicit(name, where, restore=TRUE)prohibitGeneric(name, where)registerImplicitGenerics(what, where)
name | Character string name of the function. |
where | Package or environment in which to register the implicitgenerics. When using the functions from the top level of your ownpackage source, this argument should be omitted. |
generic | Obsolete, and likely to be deprecated. |
restore | Should the non-generic version of the function berestored?. |
what | Optional table ofthe implicit generics to register, but nearly always omitted, whenit defaults to a standard metadata name. |
Multiple packages may define methods for the same function, to applyto classes defined in that package. Arithmetic and other operators,plot() and many other basic computations are typicalexamples. It's essential that all such packages write methods forthesame definition of the generic function. So long as thatgeneric uses the default choice for signature and other parameters,nothing needs to be done.
If the generic has special properties, these need to be ensured forall packages creating methods for it. The simplest solution is justto make the function generic in the package that originally ownedit. If for some reason the owner(s) of that package are unwillingto do this, the alternative is to define the correct generic,save it in a special table and restore the non-generic version bycallingsetGenericImplicit.
Note that the package containing the function can define methods for the implicit generic aswell; when the implicit generic is made a real generic, those methodswill be included.
The usual reason for having anon-default implicit generic is to provide a non-default signature,and the usual reason forthat is to allow lazy evaluation ofsome arguments. All arguments in the signature of ageneric function must be evaluated at the time the function needs toselect a method.In the base functionwith() in the example below, evaluation of the argumentexpr must be delayed; therefore, it is excluded from the signature.
If you want to completely prohibit anyone from turning your functioninto a generic, callprohibitGeneric().
FunctionimplicitGeneric() returns the implicit genericversion of the named function. If there is no table of these or ifthis function is not in the table, the result of a simple callsetGeneric(name) is returned.
FunctionimplicitGeneric() returns the implicit genericdefinition (and caches that definition the first time if it has toconstruct it).
The other functions exist for their side effect and return nothinguseful.
Implicit generic versions exist for some functions in the packagessupplied in the distribution ofR itself. These are stored in the‘methods’ package itself and will always be available.
As emphasized repeatedly in the documentation,setGeneric() calls for a function in another packageshould never have non-default settings for arguments such assignature.The reasoning applies specially to functions in supplied packages,since methods for these are likely to exist in multiple packages.A call toimplicitGeneric() will show the generic version.
### How we would make the function with() into a generic:## Since the second argument, 'expr' is used literally, we want## with() to only have "data" in the signature.## Not run: setGeneric("with", signature = "data")## Now we could predefine methods for "with" if we wanted to.## When ready, we store the generic as implicit, and restore theoriginalsetGenericImplicit("with")## End(Not run)implicitGeneric("with")# (This implicit generic is stored in the 'methods' package.)### How we would make the function with() into a generic:## Since the second argument, 'expr' is used literally, we want## with() to only have "data" in the signature.## Not run:setGeneric("with", signature="data")## Now we could predefine methods for "with" if we wanted to.## When ready, we store the generic as implicit, and restore theoriginalsetGenericImplicit("with")## End(Not run)implicitGeneric("with")# (This implicit generic is stored in the 'methods' package.)
For a class (or class definition, seegetClass andthe description of classclassRepresentation),give the names which are inherited from “above”, i.e., superclasses, rather than by this class' definition itself.
inheritedSlotNames(Class, where = topenv(parent.frame()))inheritedSlotNames(Class, where= topenv(parent.frame()))
Class | character string or |
where |
character vector of slot names, orNULL.
.srch <- search()library(stats4)inheritedSlotNames("mle")if(require("Matrix", quietly = TRUE)) withAutoprint({ inheritedSlotNames("Matrix") # NULL ## whereas inheritedSlotNames("sparseMatrix") # --> Dim & Dimnames ## i.e. inherited from "Matrix" class cl <- getClass("dgCMatrix") # six slots, etc inheritedSlotNames(cl) # *all* six slots are inherited})## Not run: ## detach package we've attached above:for(n in rev(which(is.na(match(search(), .srch))))) try( detach(pos = n) )## End(Not run).srch<- search()library(stats4)inheritedSlotNames("mle")if(require("Matrix", quietly=TRUE)) withAutoprint({ inheritedSlotNames("Matrix")# NULL## whereas inheritedSlotNames("sparseMatrix")# --> Dim & Dimnames## i.e. inherited from "Matrix" class cl<- getClass("dgCMatrix")# six slots, etc inheritedSlotNames(cl)# *all* six slots are inherited})## Not run:## detach package we've attached above:for(nin rev(which(is.na(match(search(), .srch))))) try( detach(pos= n))## End(Not run)
The arguments to functionnew to create an object from aparticular class can be interpreted specially for that class, by thedefinition of a method for functioninitialize for the class.This documentation describes some existing methods, and also outlineshow to write new ones.
signature(.Object = "ANY")The default method forinitialize takes either named orunnamed arguments. Argument names must be the names of slots inthis class definition, and the corresponding arguments must bevalid objects for the slot (that is, have the same class asspecified for the slot, or some superclass of that class). If theobject comes from a superclass, it is not coerced strictly, sonormally it will retain its current class (specifically,as(object, Class, strict = FALSE)).
Unnamed arguments must be objects of this class, of one of itssuperclasses, or one of its subclasses (from the class, from aclass this class extends, or from a class that extends thisclass). If the object is from a superclass, this normally definessome of the slots in the object. If the object is from asubclass, the new object is that argument, coerced to the currentclass.
Unnamed arguments are processed first, in the order they appear.Then named arguments are processed. Therefore, explicit valuesfor slots always override any values inferred from superclass orsubclass arguments.
signature(.Object = "traceable")Objects of a class that extendstraceable are used toimplement debug tracing (see classtraceable andtrace).
Theinitialize method for these classes takes specialargumentsdef, tracer, exit, at, print. The first of theseis the object to use as the original definition (e.g., afunction). The others correspond to the arguments totrace.
signature(.Object = "environment"),signature(.Object = ".environment")Theinitialize method for environments takes a named listof objects to be used to initialize the environment. Subclassesof"environment" inherit an initialize method through".environment", which has the additional effect ofallocating a new environment. If you define your own method forsuch a subclass, be sure either to call the existing method viacallNextMethod or allocate an environment in yourmethod, since environments are references and are not duplicatedautomatically.
signature(.Object = "signature")This is a method for internal use only.It takes an optionalfunctionDef argument to provide ageneric function with asignature slot to define theargument names. SeeMethods_Details for details.
Initialization methods provide a general mechanism corresponding togenerator functions in other languages.
The arguments toinitialize are.Object and.... Nearly always,initialize is called fromnew,not directly. The.Object argument is then theprototype object from the class.
Two techniques are often appropriate forinitialize methods:special argument names andcallNextMethod.
You may want argument names that are more natural to your users thanthe (default) slot names. These will be the formal arguments toyour method definition, in addition to.Object (always) and... (optionally). For example, the method for class"traceable" documented above would be created by a call tosetMethod of the form:
setMethod("initialize", "traceable", function(.Object, def, tracer, exit, at, print) { .... } )In this example, no other arguments are meaningful, and the resultingmethod will throw an error if other names are supplied.
When your new class extends another class, you may want to call theinitialize method for this superclass (either a special method or thedefault). For example, suppose you want to define a method for yourclass, with special argumentx, but you also want users to beable to set slots specifically. If you wantx to override theslot information, the beginning of your method definition might looksomething like this:
function(.Object, x, ...) { Object <- callNextMethod(.Object, ...) if(!missing(x)) { # do something with xYou could also choose to have the inherited method override, by firstinterpretingx, and then calling the next method.
The majority of applications using methods and classes will be inRpackages implementing new computations for an application, using newclassesof objects that represent the data and results.Computations will be implemented usingmethods that implementfunctional computations when one or more of the arguments is an objectfrom these classes.
Calls to the functionssetClass() define the new classes;calls tosetMethod define the methods.These, along with ordinaryR computations, are sufficient to getstarted for most applications.
Classes are defined in terms of the data in them and what otherclasses of data they inherit from.Section ‘Defining Classes’ outlines the basic design of new classes.
Methods areR functions, often implementing basic computations asthey apply to the new classes of objects.Section ‘Defining Methods’ discusses basic requirements andspecial tools for defining methods.
The classes discussed here are the original functional classes.R also supports formal classes and methods similar to those in otherlanguages such as Python, in which methods are part of classdefinitions and invoked on an object.These are more appropriate when computations expect references toobjects that are persistent, making changes to the object over time.SeeReferenceClasses and Chapter 9 of the reference for thechoice between these and S4 classes.
All objects inR belong to a class; ordinary vectors and other basicobjects are built-in (builtin-class).A new class is defined in terms of the namedslots that is hasand/or in terms of existing classes that it inherits from, orcontains (discussed in ‘Class Inheritance’ below).A call tosetClass() names a new class and uses the corresponding arguments todefine it.
For example, suppose we want a class of objects to represent acollection of positions, perhaps from GPS readings.A natural way to think of these inR would have vectors of numeric values forlatitude, longitude and altitude.A class with three corresponding slots could be defined by:
Pos <- setClass("Pos", slots = c(latitude = "numeric", longitude = "numeric", altitude = "numeric"))
The value returned is a function, typically assigned as here with thename of the class. Calling this function returns an object from theclass; its arguments are named with the slot names.If a function in the class had read the corresponding data, perhapsfrom a CSV file or from a data base, it could return an object fromthe class by:
Pos(latitude = x, longitude = y, altitude = z)
The slots are accessed by the@ operator; for example, ifg is an object fromthe class,g@latitude.
In addition to returning a generator function the call tosetClass() assigns a definition of the class in aspecial metadata object in the package's namespace.When the package is loaded into anR session, the class definition isadded to a table of known classes.
To make the class and the generating function publicly available, thepackage should includePOS inexportClasses() andexport() directives in itsNAMESPACE file:
exportClasses(Pos); export(Pos)
Defining methods for anR function makes that functiongeneric.Instead of a call to the function always being carried out by the samemethod, there will be several alternatives.These are selected by matching the classes of the arguments in the call to atable in the generic function, indexed by classes for one or more formal arguments to thefunction, known as thesignatures for the methods.
A method definition then specifies three things: the name of thefunction, the signature and the method definition itself.The definition must be a function with the same formal arguments asthe generic.
For example, a method to make a plot of an object from class"Pos" could be defined by:
setMethod("plot", c("Pos", "missing"), function(x, y, ...) { plotPos(x, y) })
This method will match a call toplot() if the firstargument is from class"Pos" or a subclass of that.The second argument must be missing; only a missing argument matchesthat class in the signature.Any object will match class"ANY" in the corresponding positionof the signature.
A class may inherit all the slots and methods of one or more existingclasses by specifying the names of the inherited classes in thecontains = argument tosetClass().
To define a class that extends class"Pos" to a class"GPS" with a slot for the observation times:
GPS <- setClass("GPS", slots = c(time = "POSIXt"), contains = "Pos")
The inherited classes may be S4 classes, S3classes or basic data types.S3 classes need to be identified as such by a call tosetOldClass(); most S3 classes in the base package andmany in the other built-in packages are already declared, as is"POSIXt".If it had not been, the application package should contain:
setOldClass("POSIXt")
Inheriting from one of theR types is special. Objects from the newclass will have the same type. A classCurrency that contains numeric data plus a slot"unit"would be created by
Currency <- setClass("Currency", slots = c(unit = "character"), contains = "numeric")
Objects created from this class will have type"numeric" andinherit all the builtin arithmetic and other computations for thattype.Classes can only inherit from at most one such type; if the class doesnot inherit from a type, objects from the class will have type"S4".
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Functions to test inheritance relationships between an object and aclass or between two classes (extends).
is(object, class2)extends(class1, class2, maybe = TRUE, fullInfo = FALSE)is(object, class2)extends(class1, class2, maybe=TRUE, fullInfo=FALSE)
object | anyR object. |
class1,class2 | character strings giving the names of each of the two classesbetween which |
fullInfo | In a call to |
maybe | What to return for conditional inheritance. But suchrelationships are rarely used and not recommended, so thisargument should not be needed. |
A call toselectSuperClasses(cl) returns a list ofsuperclasses, similarly toextends(cl). Additional arguments restrict the class namesreturned to direct superclasses and/or to non-virtual classes.
Either way, programming with the result, particularly usingsapply, can be useful.
To find superclasses with more generally defined properties, one can programwith the result returned byextends when called with oneclass as argument.By default, the call returns a character vector including the name of the classitself and of all its superclasses.Alternatively,ifextends is called withfullInfo = TRUE, the return value is a named list, its names being the previouscharacter vector. The elements of the list corresponding tosuperclasses are objects of classSClassExtension. Of the information in these objects, one piece can be useful:the number of generations between the classes, given by the"distance" slot.
Programming with the result of the call toextends, particularly usingsapply, can select superclasses.The programming technique is to define a test function that returnsTRUE for superclasses or relationships obeying somerequirement. For example, to find only next-to-direct superclasses,use this function with the list of extension objects:
function(what) is(what, "SClassExtension") && what@distance == 2
or, to find only superclasses from"myPkg", use this functionwith the simple vector of names:
function(what) getClassDef(what)@package == "myPkg"
Giving such functions as an argument tosapply called on the output ofextends allows you to findsuperclasses with desired properties. See the examples below.
Note that the function using extension objects must test the class of its argument since,unfortunately for this purpose, the list returned byextends includesclass1 itself, as the objectTRUE.
Prior toR 4.2.0 the code used the first elements ofclass1andclass2, silently, These are now required to be length-onecharacter vectors.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Althoughinherits is defined for S3 classes, it hasbeen modified so that the result returned is nearly always equivalent tois, both for S4 and non-S4 objects. Since it is implementedin C, it is somewhat faster.The only non-equivalences arise from use ofsetIs,which should rarely be encountered.
## Not run: ## this example can be run if package XRPython from CRAN is installed.supers <- extends("PythonInterface")## find all the superclasses from package XRfromXR <- sapply(supers, function(what) getClassDef(what)@package == "XR")## print themsupers[fromXR]## find all the superclasses at distance 2superRelations <- extends("PythonInterface", fullInfo = TRUE)dist2 <- sapply(superRelations, function(what) is(what, "SClassExtension") && what@distance == 2)## print themnames(superRelations)[dist2]## End(Not run)## Not run:## this example can be run if package XRPython from CRAN is installed.supers<- extends("PythonInterface")## find all the superclasses from package XRfromXR<- sapply(supers,function(what) getClassDef(what)@package=="XR")## print themsupers[fromXR]## find all the superclasses at distance 2superRelations<- extends("PythonInterface", fullInfo=TRUE)dist2<- sapply(superRelations,function(what) is(what,"SClassExtension")&& what@distance==2)## print themnames(superRelations)[dist2]## End(Not run)
These functions check for either a method or a class that has beensealed when it was defined, and which therefore cannot bere-defined.
isSealedMethod(f, signature, fdef, where)isSealedClass(Class, where)isSealedMethod(f, signature, fdef, where)isSealedClass(Class, where)
f | The quoted name of the generic function. |
signature | The class names in the method's signature, asthey would be supplied to |
fdef | Optional, and usually omitted: the generic functiondefinition for |
Class | The quoted name of the class. |
where | where to search for the method or class definition. Bydefault, searches from the top environment of the call to |
In theR implementation of classes and methods, it is possible toseal the definition of either a class or a method. The basicclasses (numeric and other types of vectors, matrix and array data)are sealed. So also are the methods for the primitive functions onthose data types. The effect is that programmers cannot re-definethe meaning of these basic data types and computations. Moreprecisely, for primitive functions that depend on only one dataargument, methods cannot be specified for basic classes. Forfunctions (such as the arithmetic operators) that depend on twoarguments, methods can be specified ifone of those argumentsis a basic class, but not if both are.
Programmers can seal other class and method definitions by using thesealed argument tosetClass orsetMethod.
The functions returnFALSE if the method or class is notsealed (including the case that it is not defined);TRUE ifit is.
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (For the R version.)
Chambers, John M. (1998)Programming with DataSpringer (For the original S4 version.)
## these are both TRUEisSealedMethod("+", c("numeric", "character"))isSealedClass("matrix")setClass("track", slots = c(x="numeric", y="numeric"))## but this is FALSEisSealedClass("track")## and so is thisisSealedClass("A Name for an undefined Class")## and so are these, because only one of the two arguments is basicisSealedMethod("+", c("track", "numeric"))isSealedMethod("+", c("numeric", "track"))## these are both TRUEisSealedMethod("+", c("numeric","character"))isSealedClass("matrix")setClass("track", slots= c(x="numeric", y="numeric"))## but this is FALSEisSealedClass("track")## and so is thisisSealedClass("A Name for an undefined Class")## and so are these, because only one of the two arguments is basicisSealedMethod("+", c("track","numeric"))isSealedMethod("+", c("numeric","track"))
The virtual class"language" and the specificclasses that extend it represent unevaluated objects, as produced forexample by the parser or by functions such asquote.
### each of these classes corresponds to an unevaluated object### in the S language.### The class name can appear in method signatures,### and in a few other contexts (such as some calls to as())."(""<-""call""for""if""repeat""while""name""{"### Each of the classes above extends the virtual class"language"### each of these classes corresponds to an unevaluated object### in the S language.### The class name can appear in method signatures,### and in a few other contexts (such as some calls to as())."(""<-""call""for""if""repeat""while""name""{"### Each of the classes above extends the virtual class"language"
"language" is a virtual class; no objects may be created fromit.
Objects from the other classes can be generated by a call tonew(Class, ...), whereClass is the quoted class name, andthe ... arguments are either empty or asingle object that isfrom this class (or an extension).
signature(from = "ANY", to = "call"). A methodexists foras(object, "call"), callingas.call().
showClass("language")is( quote(sin(x)) ) # "call" "language"(ff <- new("if")) ; is(ff) # "if" "language"(ff <- new("for")) ; is(ff) # "for" "language"showClass("language")is( quote(sin(x)))# "call" "language"(ff<- new("if")); is(ff)# "if" "language"(ff<- new("for")); is(ff)# "for" "language"
"LinearMethodsList"A version of methods lists that has been ‘linearized’for producing summary information. The actual objects from class"MethodsList" used for method dispatch are defined recursivelyover the arguments involved.
The functionlinearizeMlist converts an ordinary methodslist object into the linearized form.
methods:Object of class"list", the methoddefinitions.
arguments:Object of class"list", thecorresponding formal arguments, namely as many of the argumentsin the signature of the generic function as are active in therelevant method table.
classes:Object of class"list", thecorresponding classes in the signatures.
generic:Object of class"genericFunction";the generic function to which the methods correspond.
The current version oflinearizeMlist does not take advantage oftheMethodDefinition class, and therefore does more work for lesseffect than it could. In particular, we may move to redefine both thefunction and the class to take advantage of the stored signatures.Don't write code depending precisely on the present form, although allthe current information will be obtainable in the future.
FunctionlinearizeMlist for the computation,and classMethodsList for the original, recursiveform.
Local reference classes are modifiedReferenceClasses thatisolate the objects to the local frame. Therefore, they donotpropagate changes back to the calling environment. At the same time,they use the reference field semantics locally, avoiding the automaticduplication applied to standardR objects.
The current implementation has no special construction. To create alocal reference class, callsetRefClass() with acontains= argument that includes"localRefClass". Seethe example below.
Local reference classes operate essentially as do regular, functionalclasses inR; that is, changes are made by assignment and take placein the local frame.The essential difference is that replacement operations (like thechange to thetwiddle field in the example) do not causeduplication of the entire object, as would be the case for a formalclass or for data with attributes or in a named list.The purpose is to allow large objects in some fields that are notchanged along with potentially frequent changes to other fields, butwithout copying the large fields.
setRefClass(Class, fields = , contains = c("localRefClass",....), methods =, where =, ...)setRefClass(Class, fields=, contains= c("localRefClass",....), methods=, where=,...)
Localization of objects is only partially automated in the current implementation.Replacement expressions using the$<- operator are safe.
However, if reference methods for the class themselves modify fields,using<<-, for example, then one must ensure that the object is local to the relevant frame beforeany such method is called.Otherwise, standard reference class behavior still prevails.
There are two ways to ensure locality. The direct way is to invokethe specialmethodx$ensureLocal() on the object.The other way is to modify a field explicitly byx$field <- ...It'sonly necessary that one or the other of these happensonce for each object, in order to trigger the shallow copy thatprovides locality for the references. In the example below, we showboth mechanisms.
However it's done, localization must occurbefore any methods make changes. (Eventually, some use of codetools should at least largely automate this process, although it maybe difficult to guarantee success under arbitrary circumstances.)
John Chambers
## class "myIter" has a BigData field for the real (big) data## and a "twiddle" field for some parameters that it twiddles## ( for some reason)myIter <- setRefClass("myIter", contains = "localRefClass", fields = list(BigData = "numeric", twiddle = "numeric"))tw <- rnorm(3)x1 <- myIter(BigData = rnorm(1000), twiddle = tw) # OK, not REALLY bigtwiddler <- function(x, n) { x$ensureLocal() # see the Details. Not really needed in this example for(i in seq_len(n)) { x$twiddle <- x$twiddle + rnorm(length(x$twiddle)) ## then do something .... ## Snooping in gdb, etc will show that x$BigData is not copied } return(x)}x2 <- twiddler(x1, 10)stopifnot(identical(x1$twiddle, tw), !identical(x1$twiddle, x2$twiddle))## class "myIter" has a BigData field for the real (big) data## and a "twiddle" field for some parameters that it twiddles## ( for some reason)myIter<- setRefClass("myIter", contains="localRefClass", fields= list(BigData="numeric", twiddle="numeric"))tw<- rnorm(3)x1<- myIter(BigData= rnorm(1000), twiddle= tw)# OK, not REALLY bigtwiddler<-function(x, n){ x$ensureLocal()# see the Details. Not really needed in this examplefor(iin seq_len(n)){ x$twiddle<- x$twiddle+ rnorm(length(x$twiddle))## then do something ....## Snooping in gdb, etc will show that x$BigData is not copied} return(x)}x2<- twiddler(x1,10)stopifnot(identical(x1$twiddle, tw),!identical(x1$twiddle, x2$twiddle))
Constructs an object of classclassRepresentationto describe a particular class. Mostly a utility function, but you cancall it to create a class definition without assigning it, assetClass would do.
makeClassRepresentation(name, slots=list(), superClasses=character(), prototype=NULL, package, validity, access, version, sealed, virtual=NA, where)makeClassRepresentation(name, slots=list(), superClasses=character(), prototype=NULL, package, validity, access, version, sealed, virtual=NA, where)
name | character string name for the class |
slots | named list of slot classes as would be supplied to |
superClasses | what classes does this class extend |
prototype | an object providing the default data for the class,e.g., the result of a call to |
package | The character string name for the package in whichthe class will be stored; see |
validity | Optional validity method. See |
access | Access information. Not currently used. |
version | Optional version key for version control. Currentlygenerated, but not used. |
sealed | Is the class sealed? See |
virtual | Is this known to be a virtual class? |
where | The environment from which to look for classdefinitions needed (e.g., for slots or superclasses). See thediscussion of this argument underGenericFunctions. |
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (For the R version.)
Chambers, John M. (1998)Programming with DataSpringer (For the original S4 version.)
This function writes a source file containing a call tosetMethod to define a method for the generic functionand signature supplied. By default the method definition is in linein the call, but can be made an external (previously assigned) function.
method.skeleton(generic, signature, file, external = FALSE, where)method.skeleton(generic, signature, file, external=FALSE, where)
generic | the character string name of the generic function, orthe generic function itself. In the first case, the functionneed not currently be a generic, as it would not for theresulting call to |
signature | the method signature, as it would be given to |
file | a character string name for the output file, or awritable connection. By default the generic function name andthe classes in the signature are concatenated, with separatingunderscore characters. The file name should normally end in To write multiple method skeletons to one file, open the fileconnection first and then pass it to |
external | flag to control whether the function definition forthe method should be a separate external object assigned in thesource file, or included in line in the call to |
where | The environment in which to look for the function; by default,the top-level environment of the call to |
Thefile argument, invisibly, but the function is used for its side effect.
setClass("track", slots = c(x ="numeric", y="numeric"))method.skeleton("show", "track") ## writes show_track.Rmethod.skeleton("Ops", c("track", "track")) ## writes "Ops_track_track.R"## write multiple method skeletons to one filecon <- file("./Math_track.R", "w")method.skeleton("Math", "track", con)method.skeleton("exp", "track", con)method.skeleton("log", "track", con)close(con)setClass("track", slots= c(x="numeric", y="numeric"))method.skeleton("show","track")## writes show_track.Rmethod.skeleton("Ops", c("track","track"))## writes "Ops_track_track.R"## write multiple method skeletons to one filecon<- file("./Math_track.R","w")method.skeleton("Math","track", con)method.skeleton("exp","track", con)method.skeleton("log","track", con)close(con)
These classes extend the basic class"function" whenfunctions are to be stored and used as method definitions.
Method definition objects are functions with additional informationdefining how the function is being used as a method. Thetarget slot is the class signature for which the method willbe dispatched, and thedefined slot the signature for whichthe method was originally specified (that is, the one that appearedin some call tosetMethod).
The action of setting a method by a call tosetMethod creates an object of this class. It'sunwise to create them directly.
The class"SealedMethodDefinition" is created by a call tosetMethod with argumentsealed = TRUE. It hasthe same representation as"MethodDefinition".
.Data:Object of class"function"; the datapart of the definition.
target:Object of class"signature"; thesignature for which the method was wanted.
defined:Object of class"signature"; thesignature for which a method was found. If the method wasinherited, this will not be identical totarget.
generic:Object of class"character"; the functionfor which the method was created.
Class"function", from data part.
Class"PossibleMethod", directly.
Class"optionalMethod", by class"function".
classMethodsList for the objectsdefining sets of methods associated with a particular genericfunction. The individual method definitions stored in these objectsare from classMethodDefinition, or an extension.ClassMethodWithNext for an extension used bycallNextMethod.
You have navigated to an old link to documentation of S4 methods.
For basic use of classes and methods, seeIntroduction; tocreate new method definitions, seesetMethod; fortechnical details on S4 methods, seeMethods_Details.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
This documentation covers some general topics on how methodswork and how themethods package interacts with the rest ofR. Theinformation is usually not needed to get started with methods andclasses, but may be helpful for moderately ambitious projects, or whensomething doesn't work as expected.
For additional information see documentation forthe important steps: (setMethod(),setClass() andsetGeneric()). AlsoMethods_for_Nongenerics on defining formal methods forfunctions that are not currently generic functions;Methods_for_S3 for the relation to S3 classes and methods;Classes_Details for class definitions andChapters 9 and 10 of the reference.
A call to a generic function selects a method matching the actualarguments in the call. The body of the method is evaluated in theframe of the call to the generic function.A generic function is identified by its name and by the package towhich it correspond. Unlike ordinary functions, the generic has aslot that specifies its package.
In anR session, there is one version of each such generic,regardless of where the call to that generic originated, andthe generic function has a table of all the methods currentlyavailable for it; that is, all the methodsin packages currently loaded into the session.
Methods are frequently defined for functions that are non-generic intheir original package,.for example, for functionplot() inpackagegraphics.An identical version of the corresponding generic function may existin several packages. All methods will be dispatched consistentlyfrom theR session.
EachR package with a call tosetMethod in its source codewill include a methods metadata object for that generic.When the package is loaded into anR session, the methods for eachgeneric function arecached, that is, added to theenvironment of the generic function. This merged table of methods is used todispatch or select methods from the generic, using class inheritanceand possibly group generic functions (seeGroupGenericFunctions) to find an applicable method.See the “Method Selection and Dispatch” section below.The caching computations ensure that only one version of eachgeneric function is visible globally; although different attachedpackages may contain a copy of the generic function, these behaveidentically with respect to method selection.
In contrast, it is possible for the same function name to refer tomore than one generic function, when these have differentpackage slots. In the latter case,R considers thefunctions unrelated: A generic function is defined by thecombination of name and package. See the “Generic Functions”section below.
The methods for a generic are stored according to thecorrespondingsignature in the call tosetMethodthat defined the method. The signature associates oneclass name with each of a subset of the formal arguments to thegeneric function. Which formal arguments are available, and theorder in which they appear, are determined by the"signature"slot of the generic function itself. By default, the signature of thegeneric consists of all the formal arguments except ..., in theorder they appear in the function definition.
Trailing arguments in the signature of the generic will beinactive if nomethod has yet been specified that included those arguments in its signature.Inactive arguments are not needed or used in labeling the cachedmethods. (The distinction does not change which methods aredispatched, but ignoring inactive arguments improves theefficiency of dispatch.)
All arguments in the signature of the generic function will be evaluated when thefunction is called, rather than using lazyevaluation. Therefore, it's important toexcludefrom the signature any arguments that need to be dealt withsymbolically (such as theexpr argument to functionwith). Note that only actual arguments areevaluated, not default expressions.A missing argument enters into the method selection as class"missing".
The cached methods are stored in anenvironment object. The names used for assignment are aconcatenation of the class names for the active arguments in the method signature.
When a call to a generic function is evaluated, a method is selected correspondingto the classes of the actual arguments in the signature.First, the cached methods table is searched for an exact match;that is, a method stored under the signature defined bythe string value ofclass(x) for each non-missingargument, and"missing" for each missing argument.If no method is found directly for the actual arguments in a call to ageneric function, an attempt is made to match the available methods tothe arguments by using the superclass information about the actualclasses.A method found by this search is cachedin the generic function so that future calls with the same argument classes willnot require repeating the search. In any likely application, thesearch for inherited methods will be a negligible overhead.
Each class definition may include a list of one or more directsuperclasses of the new class.The simplest and most common specification is by thecontains= argument inthe call tosetClass.Each class named in this argument is a superclass of the new class.A class will also have as a direct superclass any class union to whichit is a member.Class unions are created bya call tosetClassUnion.Additional members can be added to the union by a simple call tosetIs.Superclasses specified by either mechanism are thedirect superclasses.
Inheritance specified in either of these forms issimple in thesense that all the information needed for the superclass is assertedto be directly available from the object.R inherited from S a more general form of inheritance in whichinheritance may require some transformation or be conditional on atest.This more general form has not proved to be useful in generalpractical situations. Since it also adds some computational costsnon-simple inheritance is not recommended. SeesetIsfor the general version.
The direct superclasses themselves mayhave direct superclasses andsimilarly through further generations. Putting all this information together producesthe full list of superclasses for this class.The superclass list is included in the definition of the class that iscached during theR session.Thedistance between the two classes is defined to be thenumber of generations:1 for direct superclasses (regardless of which mechanismdefined them), then2 for the direct superclasses of thoseclasses, and so on.To see all the superclasses, with their distance, print the classdefinition by callinggetClass.In addition, any class implicitly has class"ANY" as a superclass. Thedistance to"ANY" is treated as larger than the distance to anyactual class.The special class"missing" corresponding to missing argumentshas only"ANY" as a superclass, while"ANY" has nosuperclasses.
When a method is to be selected by inheritance, a search is made inthe table for all methods corresponding to a combination ofeither the direct class or one of its superclasses, for each argumentin the active signature.For an example, suppose there is only one argument in the signature and that the class ofthe corresponding object was"dgeMatrix" (from the recommended packageMatrix).This class has (currently) three direct superclasses and through theseadditional superclasses at distances 2 through 4.A method that had been defined for any of these classes or for class"ANY" (the default method) would be eligible.Methods for the shortest difference are preferred.If there is only one best method in this sense, method selection is unambiguous.
When there are multiple arguments in the signature, each argument willgenerate a similar list of inherited classes.The possible matches are now all the combinations of classes from eachargument (think of the functionouter generating an array ofall possible combinations).The search now finds all the methods matching any of this combinationof classes.For each argument, the distance to the superclass defines whichmethod(s) are preferred for that argument.A method is considered best for selection if it is among the best(i.e., has the least distance) foreach argument.
The end result is that zero, one or more methods may be “best”.If one, this method is selected and cached in the table of methods.If there is more than one best match, the selection is ambiguous and a message isprinted noting which method was selected (the first methodlexicographically in the ordering) and what other methods could havebeen selected.Since the ambiguity is usually nothing the end user could control,this is not a warning.Package authors should examine their package for possible ambiguousinheritance by callingtestInheritedMethods.
Cached inherited selections arenot themselves used in future inheritance searches, since that could resultin invalid selections.If you want inheritance computations to be done again (for example,because a newly loaded package has a more direct method than onethat has already been used in this session), callresetGeneric. Because classes and methods involvingthem tend to come from the same package, the current implementationdoes not reset all generics every time a new package is loaded.
Besides being initiated through calls to the generic function, methodselection can be done explicitly by calling the functionselectMethod.Note that some computations may use this function directly, withoptional arguments.The prime example is the use ofcoerce() methods byfunctionas().There has been some confusion from comparing coerce methods to a calltoselectMethod with other options.
Once a method has been selected, the evaluator creates a new contextin which a call to the method is evaluated.The context is initialized with the arguments from the call to thegeneric function.These arguments are not rematched. All the arguments in the signatureof the generic will have been evaluated (including any that arecurrently inactive); arguments that are not in the signature will obeythe usual lazy evaluation rules of the language.If an argument was missing in the call, its default expression if anywillnot have been evaluated, since method dispatch always usesclassmissing for such arguments.
A call to a generic function therefore has two contexts: one for thefunction and a second for the method.The argument objects will be copied to the second context, but not anylocal objects created in a nonstandard generic function.The other important distinction is that the parent(“enclosing”) environment of the second context is the environmentof the method as a function, so that allR programming techniquesusing such environments apply to method definitions as ordinary functions.
For further discussion of method selection and dispatch, see thereferences in the sections indicated.
In principle, a generic function could be any function that evaluatesa call tostandardGeneric(), the internal function that selectsa method and evaluates a call to the selected method. In practice,generic functions are special objects that in addition to being from asubclass of class"function" also extend the classgenericFunction. Such objects have slots to defineinformation needed to deal with their methods. They also havespecialized environments, containing the tables used in methodselection.
The slots"generic" and"package" in the object are thecharacter string names of the generic function itself and of thepackage from which the function is defined.As with classes, generic functions are uniquely defined inR by thecombination of the two names.There can be generic functions of the same name associated withdifferent packages (although inevitably keeping such functions cleanlydistinguished is not always easy).On the other hand,R will enforce that only one definition of ageneric function can be associated with a particular combination offunction and package name, in the current session or other activeversion ofR.
Tables of methods for a particular generic function, in this sense,will often be spread over several other packages.The total set of methods for a given generic function may changeduring a session, as additional packages are loaded.Each table must be consistent in the signature assumed for the genericfunction.
R distinguishesstandard andnonstandard genericfunctions, with the former having a function body that does nothingbut dispatch a method.For the most part, the distinction is just one of simplicity: knowingthat a generic function only dispatches a method call allows someefficiencies and also removes some uncertainties.
In most cases, the generic function is the visible functioncorresponding to that name, in the corresponding package.There are two exceptions,implicit genericfunctions and the special computations required to deal withR'sprimitive functions.Packages can contain a table of implicit generic versions of functionsin the package, if the package wishes to leave a function non-genericbut to constrain what the function would be like if it were generic.Such implicit generic functions are created during the installation ofthe package, essentially by defining the generic function andpossibly methods for it, and then reverting the function to itsnon-generic form. (SeeimplicitGeneric for how this is done.)The mechanism is mainly used for functions in the older packages inR, which may prefer to ignore S4 methods.Even in this case, the actual mechanism is only needed if somethingspecial has to be specified.All functions have a corresponding implicit generic version definedautomatically (an implicit, implicit generic function one might say).This function is a standard generic with the same arguments as thenon-generic function, with the non-generic version as the default (and only)method, and with the generic signature being all the formal argumentsexcept ....
The implicit generic mechanism is needed only to override some aspectof the default definition.One reason to do so would be to remove some arguments from thesignature.Arguments that may need to be interpreted literally, or for which thelazy evaluation mechanism of the language is needed, mustnotbe included in the signature of the generic function, since allarguments in the signature will be evaluated in order to select amethod.For example, the argumentexpr to the functionwith is treated literally and must therefore be excludedfrom the signature.
One would also need to define an implicit generic if the existingnon-generic function were not suitable as the default method.Perhaps the function only applies to some classes of objects, and thepackage designer prefers to have no general default method.In the other direction, the package designer might have some ideasabout suitable methods for some classes, if the function were generic.With reasonably modern packages, the simple approach in all thesecases is just to define the function as a generic.The implicit generic mechanism is mainly attractive for older packagesthat do not want to require the methods package to be available.
Generic functions will also be defined but not obviously visible forfunctions implemented asprimitive functions in the basepackage.Primitive functions look like ordinary functions when printed but arein fact not function objects but objects of two types interpreted bytheR evaluator to call underlying C code directly.Since their entire justification is efficiency,R refuses to hideprimitives behind a generic function object.Methods may be defined for most primitives, and corresponding metadataobjects will be created to store them.Calls to the primitive still go directly to the C code, which willsometimes check for applicable methods.The definition of “sometimes” is that methods must have beendetected for the function in some package loaded in the session andisS4(x) isTRUE for the first argument (or for thesecond argument, in the case of binary operators).You can test whether methods have been detected by callingisGeneric for the relevant function and you can examinethe generic function by callinggetGeneric, whether ornot methods have been detected.For more on generic functions, see the references and also section 2of theR Internals document supplied withR.
All method definitions are stored as objects from theMethodDefinition class.Like the class of generic functions, this class extends ordinaryRfunctions with some additional slots:"generic", containing thename and package of the generic function, and two signature slots,"defined" and"target", the first being the signature supplied whenthe method was defined by a call tosetMethod.The"target" slot starts off equal to the"defined"slot. When an inherited method is cached after being selected, asdescribed above, a copy is made with the appropriate"target" signature.Output fromshowMethods, for example, includes bothsignatures.
Method definitions are required to have the same formal arguments asthe generic function, since the method dispatch mechanism does notrematch arguments, for reasons of both efficiency and consistency.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (Section 10.5 for some details.)
For more specific information, seesetGeneric,setMethod, andsetClass.
For the use of ... in methods, seedotsMethods.
In writing methods for anR package, it's common for these methods toapply to a function (in another package) that is not generic in thatpackage; that is, there are no formal methods for the function in itsown package, although it may have S3 methods.The programming in this case involves one extra step, to callsetGeneric() to declare that the functionisgeneric in your package.
Calls to the function in your package will then use all methodsdefined there or in any other loaded package that creates the samegeneric function. Similarly, calls to the function in those packageswill use your methods.
The original version, however, remains non-generic. Callsin that package or in other packages that use that version will not dispatch your methodsexcept for special circumstances:
If the function is one of the primitive functions that acceptmethods, the internal C implementation will dispatch methods if oneof the arguments is an S4 object, as should be the case.
If the other version of the function dispatches S3 methodsand your methods are also registered as S3 methods, themethod will usually be dispatched as that S3 method.
Otherwise, you will need to ensure that all calls to thefunction come from a package in which the function is generic,perhaps by copying code to your package.
Details and the underlying reasons are discussed in the following sections.
Creating methods for a function (any function) in a package means thatcalls to the function in that package will select methods according tothe actual arguments.However, if the function was originally a non-generic in anotherpackage, calls to the function from that package willnotdispatch methods.In addition, calls from any third package that imports the non-generic versionwill also not dispatch methods.This section considers the reason and how one might deal with theconsequences.
The reason is simply theR namespace mechanism and its role inevaluating function calls.When a name (such as the name of a function) needs to be evaluated ina call to a function from some package, the evaluator looks first in the frame of the call,then in the namespace of the package and then in the imports to thatpackage.
Defining methods for a function in a package ensures that calls to thefunction in that package will select the methods, because a genericversion of the function is created in the namespace.Similarly, calls from another package that has or imports the genericversion will select methods.Because the generic versions are identical, all methods will beavailable in all these packages.
However, calls from any package that imports the old version or justselects it from the search list will usuallynot select methods.
As an example, consider the functiondata.frame() in the base package.This function takes any number of objects as arguments and attempts to combinethem as variables into a data frame object.It does this by callingas.data.frame(), also in thebase package, for each of the objects.
A reasonable goal would be to extend the classes of objects that canbe included in a data frame by defining methods foras.data.frame().But calls todata.frame(),will still use the version of that function in the base package, whichcontinues to call the non-genericas.data.frame() inthat package.
The details of what happens and options for dealing with it depend onthe form of the function: a primitive function; a function thatdispatches S3 methods; or an ordinaryR function.
Primitive functions are not actualR function objects.They go directly to internal C code.Some of them, however, have been implemented to recognize methods.These functions dispatch both S4 and S3 methods fromthe internal C code.There is no explicit generic function, either S3 or S4.The internal code looks for S4 methods if the firstargument, or either of the arguments in the case of a binary operator,is an S4 object.If no S4 method is found, a search is made for an S3 method.So defining methods for these functions works as long as the relevantclasses have been defined, which should always be the case.
A function dispatches S3 methods by callingUseMethod(), which doesnot look forformal methods regardless of whether the first argument is an S4object or not.This applies to theas.data.frame() example above.To have methods called in this situation, your package must also define themethod as an S3 method, if possible. See section ‘S3“Generic” Functions’.
In the third possibility, the function is defined with no expectationof methods.For example, the base package has a number of functions that computenumerical decompositions of matrix arguments.Some, such aschol() andqr()are implemented to dispatch S3 methods; others, such assvd() are implemented directly as a specificcomputation.A generic version of the latter functions can be written and calleddirectly to define formal methods, but no code in another package thatdoes not import this generic version will dispatch such methods.
In this case, you need to have the generic version used in all the indirect calls to thefunction supplying arguments that should dispatch methods.This may require supplying new functions that dispatch methods andthen call the function they replace.For example, if S3 methods did not work foras.data.frame(), one could call a function thatapplied the generic version to all its arguments and then calleddata.frame() as a replacement for that function.If all else fails, it might be necessary to copy over the relevantfunctions so that they would find the generic versions.
S3 method dispatch looks at the class of the firstargument.S3 methods are ordinary functions with the same arguments as thegeneric function.The “signature” of an S3 method is identified by the name towhich the method is assigned, composed of the name of thegeneric function, followed by".", followed by the name of the class.For details, seeUseMethod.
To implement a method for one of these functions corresponding to S4classes, there are two possibilities: either an S4 method or an S3 method with theS4 class name.The S3 method is only possible if the intended signature has thefirst argument and nothing else.In this case,the recommended approach is to define the S3 method and also supply theidentical function as the definition of the S4 method.If the S3 generic function wasf3(x, ...) and the S4 class forthe new method was"myClass":
f3.myClass <- function(x, ...) { ..... }
setMethod("f3", "myClass", f3.myClass)
Defining both methods usually ensures that all calls to the originalfunction will dispatch the intended method.The S4 method alone would not be called from other packages using theoriginal version of the function.On the other hand,an S3 method alone will not be called if there isanyeligible non-default S4 method.
S4 and S3 method selection are designed to follow compatible rules ofinheritance, as far as possible.S3 classes can be used for any S4 method selection, provided that theS3 classes have been registered by a call tosetOldClass, with that call specifying the correct S3inheritance pattern.S4 classes can be used for any S3 method selection; when an S4 objectis detected, S3 method selection uses the contents ofextends(class(x)) as the equivalent of the S3inheritance (the inheritance is cached after the first call).
An existing S3 method may not behave as desired for an S4 subclass, inwhich case utilities such asasS3 andS3Part may be useful. If the S3 method fails on the S4object,asS3(x) may be passed instead; if the object returnedby the S3 method needs to be incorporated in the S4 object, thereplacement function forS3Part may be useful.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Methods_for_S3 for suggested implementation of methodsthat work for both S3 and S4 dispatch.
## A class that extends a registered S3 class inherits that class' S3## methods.setClass("myFrame", contains = "data.frame", slots = c(timestamps = "POSIXt"))df1 <- data.frame(x = 1:10, y = rnorm(10), z = sample(letters,10))mydf1 <- new("myFrame", df1, timestamps = Sys.time())## "myFrame" objects inherit "data.frame" S3 methods; e.g., for `[`mydf1[1:2, ] # a data frame object (with extra attributes)## a method explicitly for "myFrame" classsetMethod("[", signature(x = "myFrame"), function (x, i, j, ..., drop = TRUE) { S3Part(x) <- callNextMethod() x@timestamps <- c(Sys.time(), as.POSIXct(x@timestamps)) x })mydf1[1:2, ]setClass("myDateTime", contains = "POSIXt")now <- Sys.time() # class(now) is c("POSIXct", "POSIXt")nowLt <- as.POSIXlt(now)# class(nowLt) is c("POSIXlt", "POSIXt")mCt <- new("myDateTime", now)mLt <- new("myDateTime", nowLt)## S3 methods for an S4 object will be selected using S4 inheritance## Objects mCt and mLt have different S3Class() values, but this is## not used.f3 <- function(x)UseMethod("f3") # an S3 generic to illustrate inheritancef3.POSIXct <- function(x) "The POSIXct result"f3.POSIXlt <- function(x) "The POSIXlt result"f3.POSIXt <- function(x) "The POSIXt result"stopifnot(identical(f3(mCt), f3.POSIXt(mCt)))stopifnot(identical(f3(mLt), f3.POSIXt(mLt)))## An S4 object selects S3 methods according to its S4 "inheritance"setClass("classA", contains = "numeric", slots = c(realData = "numeric"))Math.classA <- function(x) { (getFunction(.Generic))(x@realData) }setMethod("Math", "classA", Math.classA)x <- new("classA", log(1:10), realData = 1:10)stopifnot(identical(abs(x), 1:10))setClass("classB", contains = "classA")y <- new("classB", x)stopifnot(identical(abs(y), abs(x))) # (version 2.9.0 or earlier fails here)## an S3 generic: just for demonstration purposesf3 <- function(x, ...) UseMethod("f3")f3.default <- function(x, ...) "Default f3"## S3 method (only) for classAf3.classA <- function(x, ...) "Class classA for f3"## S3 and S4 method for numericf3.numeric <- function(x, ...) "Class numeric for f3"setMethod("f3", "numeric", f3.numeric)## The S3 method for classA and the closest inherited S3 method for classB## are not found.f3(x); f3(y) # both choose "numeric" method## to obtain the natural inheritance, set identical S3 and S4 methodssetMethod("f3", "classA", f3.classA)f3(x); f3(y) # now both choose "classA" method## Need to define an S3 as well as S4 method to use on an S3 object## or if called from a package without the S4 genericMathFun <- function(x) { # a smarter "data.frame" method for Math group for (i in seq_len(ncol(x))[sapply(x, is.numeric)]) x[, i] <- (getFunction(.Generic))(x[, i]) x}setMethod("Math", "data.frame", MathFun)## S4 method works for an S4 class containing data.frame,## but not for data.frame objects (not S4 objects)try(logIris <- log(iris)) #gets an error from the old method## Define an S3 method with the same computationMath.data.frame <- MathFunlogIris <- log(iris)## A class that extends a registered S3 class inherits that class' S3## methods.setClass("myFrame", contains="data.frame", slots= c(timestamps="POSIXt"))df1<- data.frame(x=1:10, y= rnorm(10), z= sample(letters,10))mydf1<- new("myFrame", df1, timestamps= Sys.time())## "myFrame" objects inherit "data.frame" S3 methods; e.g., for `[`mydf1[1:2,]# a data frame object (with extra attributes)## a method explicitly for "myFrame" classsetMethod("[", signature(x="myFrame"),function(x, i, j,..., drop=TRUE){ S3Part(x)<- callNextMethod() x@timestamps<- c(Sys.time(), as.POSIXct(x@timestamps)) x})mydf1[1:2,]setClass("myDateTime", contains="POSIXt")now<- Sys.time()# class(now) is c("POSIXct", "POSIXt")nowLt<- as.POSIXlt(now)# class(nowLt) is c("POSIXlt", "POSIXt")mCt<- new("myDateTime", now)mLt<- new("myDateTime", nowLt)## S3 methods for an S4 object will be selected using S4 inheritance## Objects mCt and mLt have different S3Class() values, but this is## not used.f3<-function(x)UseMethod("f3")# an S3 generic to illustrate inheritancef3.POSIXct<-function(x)"The POSIXct result"f3.POSIXlt<-function(x)"The POSIXlt result"f3.POSIXt<-function(x)"The POSIXt result"stopifnot(identical(f3(mCt), f3.POSIXt(mCt)))stopifnot(identical(f3(mLt), f3.POSIXt(mLt)))## An S4 object selects S3 methods according to its S4 "inheritance"setClass("classA", contains="numeric", slots= c(realData="numeric"))Math.classA<-function(x){(getFunction(.Generic))(x@realData)}setMethod("Math","classA", Math.classA)x<- new("classA", log(1:10), realData=1:10)stopifnot(identical(abs(x),1:10))setClass("classB", contains="classA")y<- new("classB", x)stopifnot(identical(abs(y), abs(x)))# (version 2.9.0 or earlier fails here)## an S3 generic: just for demonstration purposesf3<-function(x,...) UseMethod("f3")f3.default<-function(x,...)"Default f3"## S3 method (only) for classAf3.classA<-function(x,...)"Class classA for f3"## S3 and S4 method for numericf3.numeric<-function(x,...)"Class numeric for f3"setMethod("f3","numeric", f3.numeric)## The S3 method for classA and the closest inherited S3 method for classB## are not found.f3(x); f3(y)# both choose "numeric" method## to obtain the natural inheritance, set identical S3 and S4 methodssetMethod("f3","classA", f3.classA)f3(x); f3(y)# now both choose "classA" method## Need to define an S3 as well as S4 method to use on an S3 object## or if called from a package without the S4 genericMathFun<-function(x){# a smarter "data.frame" method for Math groupfor(iin seq_len(ncol(x))[sapply(x, is.numeric)]) x[, i]<-(getFunction(.Generic))(x[, i]) x}setMethod("Math","data.frame", MathFun)## S4 method works for an S4 class containing data.frame,## but not for data.frame objects (not S4 objects)try(logIris<- log(iris))#gets an error from the old method## Define an S3 method with the same computationMath.data.frame<- MathFunlogIris<- log(iris)
The S3 and S4 software inR are two generations implementingfunctional object-oriented programming.S3 is the original, simpler for initial programming but less general,less formal and less open to validation.The S4 formal methods and classes provide these features but requiremore programming.
In modernR, the two versions attempt to work together. Thisdocumentation outlines how to write methods for both systems bydefining an S4 method for a function that dispatches S3 methods.
The systems can also be combined by using an S3 class with S4 methoddispatch or in S4 class definitions. SeesetOldClass.
TheR evaluator will ‘dispatch’ a method from a function calleither when the body of the function calls the special primitiveUseMethod or when the call is to one of the builtinprimitives such as themath functions or the binary operators.
S3 method dispatch looks at the class of the firstargument or the class of eitherargument in a call to one of the primitive binary operators.In pure S3 situations, ‘class’ in this context means the classattribute or the implied class for a basic data type such as"numeric".The first S3 method that matches a name in the class is called and thevalue of that call is the value of the original function call.For details, seeS3Methods.
In modernR, a functionmeth in a package is registered as an S3 methodfor functionfun and classClass byincluding in the package'sNAMESPACE file the directive
S3method(fun, Class, meth)
By default (and traditionally), the third argument is taken to be thefunctionfun.Class; that is,the name of thegeneric function, followed by".", followed by the name of theclass.
As with S4 methods, a method that has been registered will be added toa table of methods for this function when the corresponding package isloaded into the session.Older versions ofR, copying the mechanism in S, looked for themethod in the current search list, but packages should now alwaysregister S3 methods rather than requiring the package to be attached.
Two possible mechanisms for implementing a method corresponding to anS4 class, there are two possibilities are to register it as an S3 method with theS4 class name or to define and set an S4 method, which will have theside effect of creating an S4 generic version of this function.
For most situations either works, butthe recommended approach is to do both: register the S3 method and supply theidentical function as the definition of the S4 method.This ensures that the proposed method will be dispatched for anyapplicable call to the function.
As an example, suppose an S4 class"uncased" is defined,extending"character" and intending to ignore upper- andlower-case.The base functionunique dispatches S3 methods.To define the class and a method for this function:
setClass("uncased", contains = "character")
unique.uncased <- function(x, incomparables = FALSE, ...) nextMethod(tolower(x))
setMethod("unique", "uncased", unique.uncased)
In addition, theNAMESPACE for the package should contain:
S3method(unique, uncased)
exportMethods(unique)
The result is to define identical S3 and S4 methods and ensure that allcalls tounique will dispatch that method when appropriate.
The reasons for defining both S3 and S4 methods are as follows:
An S4 method alone will not be seen if the S3 generic functionis called directly. This will be the case, for example, if somefunction callsunique() from a package that does not makethat function an S4 generic.
However, primitive functions and operatorsare exceptions: The internal C code will look for S4 methodsif and only if the object is an S4 object. S4 method dispatchwould be used to dispatch any binary operator calls where eitherof the operands was an S4 object, for example.
An S3 method alone will not be called if there isanyeligible non-default S4 method.
So if a package defined an S3method forunique for an S4 class but another packagedefined an S4 method for a superclass of that class, thesuperclass method would be chosen, probably not what wasintended.
S4 and S3 method selection are designed to follow compatible rules ofinheritance, as far as possible.S3 classes can be used for any S4 method selection, provided that theS3 classes have been registered by a call tosetOldClass, with that call specifying the correct S3inheritance pattern.S4 classes can be used for any S3 method selection; when an S4 objectis detected, S3 method selection uses the contents ofextends(class(x)) as the equivalent of the S3inheritance (the inheritance is cached after the first call).
For the details of S4 and S3dispatch seeMethods_Details andS3Methods.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
"MethodWithNext" Class of method definitions set up forcallNextMethod
Objects from this class are generated as a side-effect of calls tocallNextMethod.
.Data:Object of class"function"; the actualfunction definition.
nextMethod:Object of class"PossibleMethod"the method to use in response to acallNextMethod()call.
excluded:Object of class"list"; one or moresignatures excluded in finding the next method.
target:Object of class"signature", from class"MethodDefinition"
defined:Object of class"signature", fromclass"MethodDefinition"
generic:Object of class"character"; the functionfor which the method was created.
Class"MethodDefinition", directly.
Class"function", from data part.
Class"PossibleMethod", by class"MethodDefinition".
Class"optionalMethod", by class"MethodDefinition".
signature(method = "MethodWithNext"):used internally by method dispatch.
signature(object = "MethodWithNext")
callNextMethod, andclassMethodDefinition.
A call tonew returns a newly allocated object from theclass identified by the first argument. This call in turn calls themethod for the generic functioninitialize corresponding tothe specified class, passing the... arguments to thismethod.In the default method forinitialize(), named arguments providevalues for the corresponding slots and unnamed arguments must beobjects from superclasses of this class.
A call to a generating function for a class (seesetClass) will pass its ... arguments to a corresponding call tonew().
new(Class, ...)initialize(.Object, ...)new(Class,...)initialize(.Object,...)
Class | either the name of a class, a |
... | arguments to specify properties of the new object, tobe passed to |
.Object | An object: see the “Initialize Methods” section. |
The generic functioninitialize is not called directly.A call tonew begins by copying the prototype object fromthe class definition, and then callsinitialize() with thisobject as the first argument, followed by the ... arguments.
The interpretation of the... arguments in a call to agenerator function or tonew() can be specialized toparticular classes, by defining an appropriate method for"initialize".
In the default method, unnamed arguments in the... are interpreted asobjects from a superclass, and named arguments are interpreted asobjects to be assigned into the correspondingly named slots.Explicitly specified slots override inherited information for the same slot,regardless of the order in which the arguments appear.
Theinitialize methods do not have to have... astheir second argument (see the examples). Initialize methods areoften written when the natural parameters describing the new objectare not the names of the slots. If you do define such a method,you should include... as a formal argument, and your method should pass sucharguments along viacallNextMethod. This helps the definition of future subclasses of your class. If thesehave additional slots and your methoddoesnot have this argument, it will be difficult for theseslots to be included in an initializing call.
Seeinitialize-methods for a discussion of some classes with existingmethods.
Methods forinitialize can be inherited only by simpleinheritance, since it is a requirement that the method return anobject from the target class. See thesimpleInheritanceOnly argument tosetGeneric andthe discussion insetIs for the general concept.
Note that the basic vector classes,"numeric", etc. areimplicitly defined, so one can usenew for these classes.The ... arguments are interpreted as objects of this type and areconcatenated into the resulting vector.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Classes_Details for details of class definitions, andsetOldClass for the relation to S3 classes.
## using the definition of class "track" from \link{setClass}## a new object with two slots specifiedt1 <- new("track", x = seq_along(ydata), y = ydata)# a new object including an object from a superclass, plus a slott2 <- new("trackCurve", t1, smooth = ysmooth)### define a method for initialize, to ensure that new objects have### equal-length x and y slots. In this version, the slots must still be### supplied by name.setMethod("initialize", "track", function(.Object, ...) { .Object <- callNextMethod() if(length(.Object@x) != length(.Object@y)) stop("specified x and y of different lengths") .Object })### An alternative version that allows x and y to be supplied### unnamed. A still more friendly version would make the default x### a vector of the same length as y, and vice versa.setMethod("initialize", "track", function(.Object, x = numeric(0), y = numeric(0), ...) { .Object <- callNextMethod(.Object, ...) if(length(x) != length(y)) stop("specified x and y of different lengths") .Object@x <- x .Object@y <- y .Object })## using the definition of class "track" from \link{setClass}## a new object with two slots specifiedt1<- new("track", x= seq_along(ydata), y= ydata)# a new object including an object from a superclass, plus a slott2<- new("trackCurve", t1, smooth= ysmooth)### define a method for initialize, to ensure that new objects have### equal-length x and y slots. In this version, the slots must still be### supplied by name.setMethod("initialize","track",function(.Object,...){ .Object<- callNextMethod()if(length(.Object@x)!= length(.Object@y)) stop("specified x and y of different lengths") .Object})### An alternative version that allows x and y to be supplied### unnamed. A still more friendly version would make the default x### a vector of the same length as y, and vice versa.setMethod("initialize","track",function(.Object, x= numeric(0), y= numeric(0),...){ .Object<- callNextMethod(.Object,...)if(length(x)!= length(y)) stop("specified x and y of different lengths") .Object@x<- x .Object@y<- y .Object})
S4 classes that are defined to extend one of the basicvector classes should contain the classstructure if they behave like structures; thatis, if they should retain their class behavior under math functionsor operators, so long as their length is unchanged.On the other hand, if their class depends on the values in theobject, not just its structure, then they should lose that classunder any such transformations. In the latter case, they should bedefined to containnonStructure.
If neither of these strategies applies, the class likely needs somemethods of its own forOps,Math, and/orother generic functions. What is not usually a good idea is to allowsuch computations to drop down to the default, base code. This isinconsistent with most definitions of such classes.
Methods are defined for operators and math functions (groupsOps,Math andMath2). Inall cases the result is an ordinary vector of the appropriate type.
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer.
setClass("NumericNotStructure", contains = c("numeric","nonStructure"))xx <- new("NumericNotStructure", 1:10)xx + 1 # vectorlog(xx) # vectorsample(xx) # vectorsetClass("NumericNotStructure", contains= c("numeric","nonStructure"))xx<- new("NumericNotStructure",1:10)xx+1# vectorlog(xx)# vectorsample(xx)# vector
This class of objects is used to represent ordinary character stringobject names, extended with apackage slot naming the packageassociated with each object.
The functiongetGenerics returns an object of this class.
.Data:Object of class"character": theobject names.
package:Object of class"character" thepackage names.
Class"character", from data part.
Class"vector", by class"character".
Methods for general background.
Assembles all relevant slot and method information for a class, withminimal markup for Rd processing; no QC facilities at present.
promptClass(clName, filename = NULL, type = "class", keywords = "classes", where = topenv(parent.frame()), generatorName = clName)promptClass(clName, filename=NULL, type="class", keywords="classes", where= topenv(parent.frame()), generatorName= clName)
clName | a character string naming the class to be documented. |
filename | usually, a connection or a character string giving thename of the file to which the documentation shell should be written.The default corresponds to a file whose name is the topic name forthe class documentation, followed by |
type | the documentation type to be declared in the output file. |
keywords | the keywords to include in the shell of thedocumentation. The keyword |
where | where to look for the definition of the class and ofmethods that use it. |
generatorName | the name for a generator function for thisclass; only required if a generator function was createdand saved under a name different from the class name. |
The class definition is found on the search list. Using thatdefinition, information about classes extended and slots isdetermined.
In addition, the currently available generics with methods for thisclass are found (usinggetGenerics). Note that thesemethods need not be in the same environment as the class definition; inparticular, this part of the output may depend on which packages arecurrently in the search list.
As with other prompt-style functions, unlessfilename isNA, the documentation shell is written to a file, and a messageabout this is given. The file will need editing to give informationabout themeaning of the class. The output ofpromptClass can only contain information from the metadataabout the formal definition and how it is used.
Iffilename isNA, a list-style representation of thedocumentation shell is created and returned. Writing the shell to afile amounts tocat(unlist(x), file = filename, sep = "\n"),wherex is the list-style representation.
If a generator function is found assigned under the class name orthe optionalgeneratorName, skeleton documentation for thatfunction is added to the file.
Iffilename isNA, a list-style representation of thedocumentation shell. Otherwise, the name of the file written to isreturned invisibly.
VJ Carey[email protected] and John Chambers
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (For the R version.)
Chambers, John M. (1998)Programming with DataSpringer (For the original S4 version.)
prompt for documentation of functions,promptMethods for documentation of method definitions.
For processing of the edited documentation, either useR CMDRdconv,or include the edited file in the ‘man’ subdirectory of apackage.
## Not run: > promptClass("track")A shell of class documentation has been written to thefile "track-class.Rd".## End(Not run)## Not run: > promptClass("track")A shell of class documentation has been written to thefile"track-class.Rd".## End(Not run)
Generates a shell of documentation for the methods of a genericfunction.
promptMethods(f, filename = NULL, methods)promptMethods(f, filename=NULL, methods)
f | a character string naming the generic function whose methodsare to be documented. |
filename | usually, a connection or a character string giving thename of the file to which the documentation shell should be written.The default corresponds to the coded topic name for these methods(currently, |
methods | optional If this argument is supplied, it is likely to be |
Iffilename isFALSE, the text created is returned,presumably to be inserted some other documentation file, such as thedocumentation of the generic function itself (seeprompt).
Iffilename isNA, a list-style representation of thedocumentation shell is created and returned. Writing the shell to afile amounts tocat(unlist(x), file = filename, sep = "\n"),wherex is the list-style representation.
Otherwise, the documentation shell is written to the file specified byfilename.
Iffilename isFALSE, the text generated;iffilename isNA, a list-style representation of thedocumentation shell.Otherwise, the name of the file written to is returned invisibly.
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (For the R version.)
Chambers, John M. (1998)Programming with DataSpringer (For the original S4 version.)
The software described here allows packages to definereferenceclasses that behave in the style of “OOP” languages such as Java andC++.This model forOOP differs from the functional model implemented by S4(and S3) classes and methods, in which methods are defined for genericfunctions.Methods for reference classes are “encapsulated” in the class definition.
Computations with objects from reference classes invoke methods on them andextract or set their fields, using the`$` operator inR.The field and method computations potentially modify the object.All computations referring to the objects see the modifications, in contrast tothe usual functional programming model inR.
A call tosetRefClass in the source code for a package defines the class and returns a generator object.Subsequent calls to the$methods() method of the generator will define methods for the class.As with functional classes, if the class is exported from the package,it will be available when the package is loaded.
Methods areR functions. In their usual implementation, they refer to fieldsand other methods of the class directly by name. See the section on“Writing Reference Methods”.
As with functional classes, reference classes can inherit from otherreference classes via acontains= argument tosetRefClass. Fields and methods will be inherited, except where thenew class overrides method definitions. See the section on “Inheritance”.
setRefClass(Class, fields = , contains = , methods =, where =, inheritPackage =, ...)getRefClass(Class, where =)setRefClass(Class, fields=, contains=, methods=, where=, inheritPackage=,...)getRefClass(Class, where=)
Class | character string name for the class. In the call to |
fields | either a character vector of field names ora named list of the fields. The resulting fields will be accessed with reference semantics (seethe section on “Reference Objects”). If the argument is a list, eachelement of the list should usually be the character string name of a class, inwhich case the object in the field must be from that class or asubclass. An alternative, but not generally recommended, is to supply anaccessorfunction; see the section on “Implementation” for accessorfunctions and the related internal mechanism. Note that fields are distinct fromslots. Reference classes should not define class-specific slots. Seethe note on slots in the“Implementation” section. |
contains | optional vector of superclasses for this class. If a superclass isalso a reference class, the fields and class-based methods will be inherited. |
methods | a named list of function definitions that can be invoked on objectsfrom this class. These can also be created by invoking the |
where | for For |
inheritPackage | Should objects from the new class inherit the package environment of acontained superclass? Default |
... | other arguments to be passed to |
setRefClass() returns a generator function suitable forcreating objects from the class, invisibly. A call to this functiontakes any number of arguments,which will be passed on to the initialize method. If noinitialize method is defined for the class or one of itssuperclasses, the default method expects named arguments with thename of one of the fields and unnamed arguments, if any, that areobjects from one of the superclasses of this class (but onlysuperclasses that are themselves reference classes have any effect).
The generator function is similar to the S4 generator functionreturned bysetClass. In addition to being a generatorfunction, however, it is also a reference class generator object,with reference class methods for various utilities. See the sectionon reference class generator objects below.
getRefClass() also returns the generator function for theclass. Note that the package slot in the value is the correct packagefrom the class definition, regardless of thewhere argument,which is used only tofind the class if necessary.
Normal objects inR are passed as arguments in function calls consistently withfunctional programming semantics; that is, changes made to an objectpassed as an argument are local to the function call. The object thatsupplied the argument is unchanged.
The functional model (sometimes called pass-by-value, although this isinaccurate forR) issuitable for many statistical computations and is implicit, forexample, in the basicR software for fitting statistical models.In some other situations, one would like all the code dealing with anobject to see the exact same content, so that changes made in anycomputation would be reflected everywhere.This is often suitable if the object has some “objective”reality, such as a window in a user interface.
In addition, commonly used languages, including Java, C++ and manyothers, support a version of classes and methods assuming referencesemantics.The corresponding programming mechanismis to invoke a method on an object.In theR syntax we use"$" for this operation; one invokes a method,m1 say, on an objectx by the expressionx$m1(...).
Methods in this paradigm are associated with the object, or moreprecisely with the class of the object, as opposed to methods in afunction-based class/method system, which are fundamentally associatedwith the function (inR, for example, a generic function in anRsession has a table of all its currently known methods).In this document “methods for a class” as opposed to“methods for a function” will make the distinction.
Objects in this paradigm usually have named fields on whichthe methods operate.In theR implementation, the fields are defined when the class iscreated.The field itself can optionally have a specified class, meaning that only objectsfrom this class or one of its subclasses can be assigned to the field.By default, fields have class"ANY".
Fields are accessed by reference.In particular, invoking a method may modify the content ofthe fields.
Programming for such classes involves writing new methods for aparticular class.In theR implementation, these methods areR functions, with zero ormore formal arguments.For standard reference methods, the object itself is not an explicitargument to the method.Instead, fields and methods for the class can be referred to by namein the method definition.The implementation usesR environments to make fields and other methodsavailable by name within the method.Specifically, the parent environment of the method is the object itself.See the section on “WritingReference Methods”.This special use of environments is optional. If a method is definedwith an initial formal argument.self, that will be passed inas the whole object, and the method follows the standard rules for anyfunction in a package. See the section on “External Methods”
The goal of the software described here is to provide a uniformprogramming style inR for software dealing with reference classes, whetherimplemented directly inR or through an interface to one of theOOPlanguages.
Reference methods are functions supplied as elements of a named list,eitherwhen invoking$methods() on a generator objectg or asthe argumentmethods in a call tosetRefClass.The two mechanisms have the same effect, but the first makes the code more readable.
Methods are written as ordinaryR functions but have some specialfeatures and restrictions in their usual form.In contrast to some other languages (e.g., Python), the object itselfdoes not need to be an argument in the method definition.The body of the function can contain calls to any other reference method,including those inherited from other reference classes and may referto methods and to fields in the object by name.
Alternatively, a method may be anexternal method.This is signalled by.self being the first formal argument to the method.The body of the method then works like any ordinary function.The methods are called like other methods (without the.selfargument, which is supplied internally and always refers to the objectitself).Inside the method, fields and other methods are accessed in the form.self$x. External methods exist so that reference classes can inherit thepackage environment of superclassesin other packages; see the section on “External Methods”.
Fields may be modified in a method by using thenon-local assignment operator,<<-, as in the$edit and$undomethods in the example below.Note that non-local assignment is required: a local assignment withthe<- operator just creates a local object in the functioncall, as it would in anyR function.When methods are installed, a heuristic check is made for localassignments to field names and a warning issued if any are detected.
Reference methods should be kept simple; if they need to do somespecializedR computation, that computation should use a separateRfunction that is called from the reference method.Specifically, methods can not use special features of theenclosing environment mechanism, since the method's environment isused to access fields and other methods.In particular, methods should not use non-exported entries in thepackage's namespace, because the methods may be inherited by areference class in another package.
Two method names are interpreted specially,initializeandfinalize. If aninitialize method is defined, itwill be invoked when an object is generated from the class. See thediscussion of method$new(...) in the section “Initialization Methods”.
If afinalize method is defined, a function will beregistered to invoke it before the environment inthe object is discarded by the garbage collector; finalizers areregistered withatexit=TRUE, and so are also run at the end ofR sessions. See the matrix viewer example for both initialize andfinalize methods.
Reference methods can not themselves be generic functions; if you wantadditional function-based method dispatch, write a separate genericfunction and call that from the method.
Two special object names are available.The entire object can be referred to in a method by the reservedname.self.The object.refClassDef contains the definition of theclass of the object.These are accessed as fields but are read-only, with one exception.In principal, the.self field can be modified in the$initialize method, because the object is still being created at this stage.This is not recommended, as it can invalidate the object with respectto its class.
The methods available include methods inherited from superclasses, asdiscussed in the section “Inheritance”.
Only methods actually used will be included in the environmentcorresponding to an individual object. To declare that a method requires aparticular other method, the first method should include a callto$usingMethods() with the name of the other method as an argument.Declaring the methods this way is essential if the other method is used indirectly (e.g., viasapply()ordo.call()).If it is called directly, code analysis will find it.Declaring the method is harmless in any case, however, and may aidreadability of the source code.
Documentation for the methods can be obtained by the$help method for the generator object.Methods for classes are not documented in theRd format usedforR functions.Instead, the$help method prints the calling sequence of the method, followed byself-documentation from the method definition, in the style of Python.If the first element of the body of the method is a literal characterstring (possibly multi-line), that string is interpreted as documentation.See the method definitions in the example.
If the class has a method defined for$initialize(), this method will be called once the reference object has beencreated. You should write such a method for a class that needs to dosome special initialization.In particular, a reference method is recommended rather than a methodfor the S4 generic functioninitialize(), because some special initialization isrequired for reference objectsbefore the initialization offields.As with S4 classes, methods are written for$initialize() and not for$new(), both for the previous reason and also because$new() is invoked on the generator object and would be a method for that class.
The default method for$initialize() is equivalent to invoking the method$initFields(...). Named arguments assign initial values to the corresponding fields.Unnamed arguments must be objects from this class or a referencesuperclass of this class.Fields will be initialized to the contents of the fields in suchobjects, but named arguments override the corresponding inheritedfields.Note that fields are simply assigned. If the field is itself areference object, that object is not copied.The new and previous object will share the reference.Also, a field assigned from an unnamed argument counts as anassignment for locked fields.To override an inherited value for a locked field, the new value mustbe one of the named arguments in the initializing call.A later assignment of the field will result in an error.
Initialization methods need some care in design.The generatorfor a reference class will be called with no arguments, for examplewhen copying the object.To ensure that these calls do not fail, the method must have defaultsfor all arguments or check formissing().The methodshould include... as an argument andpass this on via$callSuper() (or$initFields() ifyou know that your superclasses have no initialization methods).This allows future class definitions that subclass this class, withadditional fields.
Reference classes inherit from other reference classes by using thestandardR inheritance; that is, by including the superclasses in thecontains= argument when creating the new class.The names of the reference superclasses are in slotrefSuperClasses of the class definition.Reference classes can inherit from ordinary S4 classes also, but thisis usually a bad idea if it mixes reference fields and non-reference slots.See the comments in the section on “Implementation”.
Class fields are inherited. A class definition can override a fieldof the same name in a superclass only if the overriding class is asubclass of the class of the inherited field. This ensures that avalid object in the field remains valid for the superclass as well.
Inherited methods are installed in the same way as directlyspecified methods.The code in a method can refer to inherited methods in the sameway as directly specified methods.
A method may override a method of the same name in a superclass.The overriding method can call the superclass method bycallSuper(...) as described below.
All reference classes inherit from the class"envRefClass".All reference objects can use the following methods.
$callSuper(...)Calls the method inherited from a reference superclass.The call is meaningful only from within another method, and will beresolved to call the inherited method of the same name.The arguments to$callSuper are passed to the superclass version.See the matrix viewer class in the example.
Note that the intended arguments for the superclass method must besupplied explicitly; there is no convention for supplying thearguments automatically, in contrast to the similar mechanism forfunctional methods.
$copy(shallow = FALSE)Creates a copy of the object. With reference classes, unlike ordinaryR objects, merely assigning the object with a different name does notcreate an independent copy. Ifshallow isFALSE, anyfield that is itself a reference object will also be copied, andsimilarly recursively for its fields. Otherwise, while reassigning afield to a new reference object will have no side effect, modifyingsuch a field will still be reflected in both copies of the object.The argument has no effect on non-reference objects in fields. Whenthere are reference objects in some fields but it is asserted thatthey will not be modified, usingshallow = TRUE will save somememory and time.
$field(name, value)With one argument, returns the field of the object with characterstringname. With two arguments, the corresponding field isassignedvalue. Assignment checks thatname specifies avalid field, but the single-argument version will attempt to getanything of that name from the object's environment.
The$field() method replaces the direct use of a field name, when the name of thefield must be calculated, or for looping over several fields.
$export(Class)Returns the result of coercing the object toClass (typicallyone of the superclasses of the object's class). Calling the methodhas no side effect on the object itself.
$getRefClass();$getClass()These return respectively the generator object and the formal classdefinition for the reference class of this object, efficiently.
$import(value, Class = class(value))Import the objectvalue into the current object, replacing thecorresponding fields in the current object.Objectvalue must come from one of the superclasses of thecurrent object's class.If argumentClass is supplied,value is first coerced tothat class.
$initFields(...)Initialize the fields of the object from the supplied arguments. Thismethod is usually only called from a class with a$initialize()method. It corresponds to the default initialization for referenceclasses. If there are slots and non-reference superclasses, these maybe supplied in the ... argument as well.
Typically, a specialized$initialize()method carries out its own computations, then invokes$initFields()to perform standard initialization, as shown in thematrixViewer class in the example below.
$show()This method is called when the object is printed automatically,analogously to theshow function. A general method isdefined for class"envRefClass". User-defined referenceclasses will often define their own method: see the Example below.
Note two points in the example. As with anyshow() method, itis a good idea to print the class explicitly to allow for subclassesusing the method. Second, to call thefunctionshow()from the method, as opposed to the$show() method itself, refer tomethods::show() explicitly.
$trace(what, ...),$untrace(what)Apply the tracing and debugging facilities of thetracefunction to the reference methodwhat.
All the arguments of thetracefunction can be supplied, except forsignature, which is notmeaningful.
The reference method can be invoked on either an object or thegenerator for the class. See the section on Debugging below for details.
$usingMethods(...)Reference methods used by this method are named as the argumentseither quoted or unquoted. In the code analysis phase of installingthe present method, the declared methods will be included. It is essentialto declare any methods used in a nonstandard way (e.g., via an apply function).Methods called directly do not need to be declared, but it is harmless to do so.$usingMethods() does nothing at run time.
Objects also inherit two reserved fields:
.selfa reference to the entire object;
.refClassDefthe class definition.
The defined fields should not override these, and in general it isunwise to define a field whose name begins with".", since theimplementation may use such names for special purposes.
The environment of a method in a reference class is the object itself,as an environment.This allows the method to refer directly to fields and other methods,without using the whole object and the"$" operator.The parent of that environment is the namespace of the package inwhich the reference class is defined.Computations in the method have access to all the objects in thepackage's namespace, exported or not.
When defining a class that contains a reference superclass in anotherpackage, there is an ambiguity about which package namespace shouldhave that role.The argumentinheritPackage tosetRefClass() controlswhether the environment of new objects should inherit from aninherited class in another package or continue to inherit from thecurrent package's namespace.
If the superclass is “lean”, with few methods, or existsprimarily to support a family of subclasses, then it may be better tocontinue to use the new package's environment.On the other hand, if the superclass was originally written as astandalone, this choice may invalidate existing superclass methods.For the superclass methods to continue to work, they must use onlyexported functions in their package and the new package must importthese.
Either way, some methods may need to be written that donotassume the standard model for reference class methods, but behaveessentially as ordinary functions would in dealing with referenceclass objects.
The mechanism is to recognizeexternal methods.An external method iswritten as a function in which the first argument, named.self,stands for the reference class object.This function is supplied as the definition for a reference class method.The method will be called, automatically, with the first argumentbeing the current object and the other arguments, if any, passed alongfrom the actual call.
Since an external method is an ordinary function in the source codefor its package, it has access to all the objects in the namespace.Fields and methods in the reference class must be referred to in theform.self$name.
If for some reason you do not want to use.self as the firstargument, a functionf() can be converted explicitly asexternalRefMethod(f), which returns an object of class"externalRefMethod" that can be supplied as a method for theclass.The first argument will still correspond to the whole object.
External methods can be supplied for any reference class, but there is noobvious advantage unless they are needed.They are more work to write, harder to read and (slightly) slower toexecute.
NOTE: If you are the author of a package whose referenceclasses are likely to be subclassed in other packages, you can avoidthese questions entirely by writing methods thatonly useexported functions from your package, so that all the methods willwork from another package that imports yours.
The call tosetRefClass defines the specified class andreturns a “generator function” object for that class.This object has class"refObjectGenerator"; it inheritsfrom"function" via"classGeneratorFunction" and can becalled to generate new objects from the reference class.
The returned object is also a reference class object, although not ofthe standard construction.It can be used to invoke reference methods and access fields in the usual way, butinstead of being implemented directly as an environment it has asubsidiary generator object as a slot, astandard reference object (of class"refGeneratorSlot").Note that if one wanted to extend the reference class generatorcapability with a subclass, this should be done by subclassing"refGeneratorSlot", not"refObjectGenerator".
The fields aredef, the class definition, andclassName,the character string name of the class.Methods generate objectsfrom the class, to access help on reference methods, and todefine new reference methods for the class.The currently available methods are:
$new(...)This method is equivalent to calling the generator function returnedbysetRefClass.
$help(topic)Prints brief help on the topic. The topics recognizedare reference method names, quoted or not.
The information printed is the calling sequence for the method, plusself-documentation if any.Reference methods can have an initial character string or vector asthe first element in the body of the function defining the method.If so, this string is taken as self-documentation for the method (seethe section on “Writing Reference Methods” for details).
If no topic is given or if the topic is not a method name, thedefinition of the class is printed.
$methods(...)With no arguments, returns the names of the reference methods for thisclass.With one character string argument, returns the method of that name.
Named argumentsare method definitions, which will beinstalled in the class, as if they had been supplied in themethods argument tosetRefClass().Supplying methods in this way, rather than in the call tosetRefClass(), is recommended for the sake of clearer sourcecode.See the section on “Writing Reference Methods” for details.
All methods for a class should be defined in the source code thatdefines the class, typically as part of a package.In particular, methods can not be redefined in a class in an attachedpackage with a namespace: The class method checks for a lockedbinding of the class definition.
The new methods can refer to any currently defined method by name(including other methods supplied in this call to$methods()). Note though that previously defined methods are not re-analyzedmeaning that they will not call the new method (unless it redefines anexisting method of the same name).
To remove a method, supplyNULL as its new definition.
$fields()Returns a list of the fields, each with its corresponding class.Fields for which an accessor function was supplied in the definitionhave class"activeBindingFunction".
$lock(...)The fields named in the arguments are locked; specifically, after thelock method is called, the field may be set once. Any further attemptto set it will generate an error.
If called with no arguments, the method returns the names of thelocked fields.
Fields that are defined by an explicit accessor function can not belocked (on the other hand, the accessor function can be defined togenerate an error if called with an argument).
All code to lock fields should normally be part of the definition of aclass; that is, the read-only nature of the fields is meant to be partof the class definition, not a dynamic property added later.In particular, fields can not be locked in a class in an attachedpackage with a namespace: The class method checks for a lockedbinding of the class definition. Locked fields can not besubsequently unlocked.
$trace(what, ..., classMethod = FALSE)Establish a traced version of methodwhat for objects generatedfrom this class. The generator object tracing works like the$trace()method for objects from the class, with two differences.Since it changes the method definition in the class object itself,tracing applies to all objects, not just the one on which the tracemethod is invoked.
Second, the optional argumentclassMethod = TRUE allows tracingon the methods of the generator object itself.By default,what is interpreted as the name of a method in theclass for which this object is the generator.
$accessors(...)Reference classes are implemented as S4 classes with a data part oftype"environment".Fields correspond to named objects in the environment.A field associated with a function is implemented as anactive binding.In particular, fields with a specified class are implemented as aspecial form of active binding to enforce valid assignment to thefield.
As a related feature, the element in thefields= list suppliedtosetRefClass can be anaccessorfunction, a function of one argument that returnsthe field if called with no argument or sets it to the value of theargument otherwise.Accessor functions are used internally and for inter-system interfaceapplications, but not generally recommended as they blur the conceptof fields as data within the object.
A field, saydata, can be accessed generally by an expressionof the formx$data for any object from the relevant class.In an internal method for this class, the field can be accessed by the namedata.A field that is not locked can be set by an expression of the formx$data <- value.Inside an internal method, a field can be assigned by an expression of the formx <<- value.Note thenon-local assignment operator.The standardR interpretation of this operator works to assign it inthe environment of the object.If the field has an accessor function defined, getting and settingwill call that function.
When a method is invoked on an object, the function defining the method isinstalled in the object's environment, with the same environment as theenvironment of the function.
Reference classes can have validity methods in the same sense as anyS4 class (seesetValidity).Such methods are often a good idea; they will be called by callingvalidObject and a validity method, if one is defined,will be called when a reference object is created (from version 3.4 ofR on).Just remember that these are S4 methods. The function will be calledwith theobject as its argument. Fields and methods must beaccessed using$.
Note: Slots. Because of the implementation, new reference classes can inherit fromnon-reference S4 classes as well as reference classes, and can includeclass-specific slots in the definition.This is usually a bad idea, if the slots from the non-referenceclass are thought of as alternatives to fields.Slots will as always be treated functionally.Therefore, changes to the slots and the fields will behave inconsistently,mixing the functionaland reference paradigms for properties of the same object,conceptually unclear and prone to errors.In addition, the initialization method for the class will have to sortout fields from slots, with a good chance of creating anomalousbehavior for subclasses of this class.
Inheriting from aclass union, however, is a reasonable strategy (withall members of the union likely to be reference classes).
The standardR debugging and tracing facilities can be applied toreference methods.Reference methods can be passed todebug and itsrelatives from an object to debug further method invocations on thatobject; for example,debug(xx$edit).
Somewhat more flexible use is available for a reference method versionof thetrace function.A corresponding$trace() reference method is available foreither an object or for the reference class generator(xx$trace() ormEdit$trace() in the example below).Using$trace() on an object sets up a tracingversion for future invocations of the specified method for thatobject.Using$trace() on the generator for the class sets up atracing version for all future objects from that class (and sometimes forexisting objects from the class if the method is not declared orpreviously invoked).
In either case, all the arguments to the standardtracefunction are available, except forsignature= which ismeaningless since reference methods can not be S4 generic functions.This includes the typical styletrace(what, browser) forinteractive debugging andtrace(what, edit = TRUE) to edit thereference method interactively.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 11.)
## a simple editor for matrix objects. Method $edit() changes some## range of values; method $undo() undoes the last edit.mEdit <- setRefClass("mEdit", fields = list( data = "matrix", edits = "list"))## The basic edit, undo methodsmEdit$methods( edit = function(i, j, value) { ## the following string documents the edit method 'Replaces the range [i, j] of the object by value. ' backup <- list(i, j, data[i,j]) data[i,j] <<- value edits <<- c(edits, list(backup)) invisible(value) }, undo = function() { 'Undoes the last edit() operation and update the edits field accordingly. ' prev <- edits if(length(prev)) prev <- prev[[length(prev)]] else stop("No more edits to undo") edit(prev[[1]], prev[[2]], prev[[3]]) ## trim the edits list length(edits) <<- length(edits) - 2 invisible(prev) })## A method to automatically print objectsmEdit$methods( show = function() { 'Method for automatically printing matrix editors' cat("Reference matrix editor object of class", classLabel(class(.self)), "\n") cat("Data: \n") methods::show(data) cat("Undo list is of length", length(edits), "\n") } )xMat <- matrix(1:12,4,3)xx <- mEdit(data = xMat)xx$edit(2, 2, 0)xxxx$undo()mEdit$help("undo")stopifnot(all.equal(xx$data, xMat))utils::str(xx) # show fields and names of methods## A method to save the objectmEdit$methods( save = function(file) { 'Save the current object on the file in R external object format. ' base::save(.self, file = file) })tf <- tempfile()xx$save(tf)## Not run: ## Inheriting a reference class: a matrix viewermv <- setRefClass("matrixViewer", fields = c("viewerDevice", "viewerFile"), contains = "mEdit", methods = list( view = function() { dd <- dev.cur(); dev.set(viewerDevice) devAskNewPage(FALSE) matplot(data, main = paste("After",length(edits),"edits")) dev.set(dd)}, edit = # invoke previous method, then replot function(i, j, value) { callSuper(i, j, value) view() }))## initialize and finalize methodsmv$methods( initialize = function(file = "./matrixView.pdf", ...) { viewerFile <<- file pdf(viewerFile) viewerDevice <<- dev.cur() dev.set(dev.prev()) callSuper(...) }, finalize = function() { dev.off(viewerDevice) })## debugging an object: call browser() in method $edit()xx$trace(edit, browser)## debugging all objects from class mEdit in method $undo()mEdit$trace(undo, browser)## End(Not run)## a simple editor for matrix objects. Method $edit() changes some## range of values; method $undo() undoes the last edit.mEdit<- setRefClass("mEdit", fields= list( data="matrix", edits="list"))## The basic edit, undo methodsmEdit$methods( edit=function(i, j, value){## the following string documents the edit method 'Replaces the range[i, j] of the object by value. ' backup<- list(i, j, data[i,j]) data[i,j]<<- value edits<<- c(edits, list(backup)) invisible(value)}, undo=function(){ 'Undoes the last edit() operation and update the edits field accordingly. ' prev<- editsif(length(prev)) prev<- prev[[length(prev)]]else stop("No more edits to undo") edit(prev[[1]], prev[[2]], prev[[3]])## trim the edits list length(edits)<<- length(edits)-2 invisible(prev)})## A method to automatically print objectsmEdit$methods( show=function(){'Method for automatically printing matrix editors' cat("Reference matrix editor object of class", classLabel(class(.self)),"\n") cat("Data: \n") methods::show(data) cat("Undo list is of length", length(edits),"\n")})xMat<- matrix(1:12,4,3)xx<- mEdit(data= xMat)xx$edit(2,2,0)xxxx$undo()mEdit$help("undo")stopifnot(all.equal(xx$data, xMat))utils::str(xx)# show fields and names of methods## A method to save the objectmEdit$methods( save=function(file){ 'Save the current object on the filein R external object format. ' base::save(.self, file= file)})tf<- tempfile()xx$save(tf)## Not run:## Inheriting a reference class: a matrix viewermv<- setRefClass("matrixViewer", fields= c("viewerDevice","viewerFile"), contains="mEdit", methods= list( view=function(){ dd<- dev.cur(); dev.set(viewerDevice) devAskNewPage(FALSE) matplot(data, main= paste("After",length(edits),"edits")) dev.set(dd)}, edit=# invoke previous method, then replotfunction(i, j, value){ callSuper(i, j, value) view()}))## initialize and finalize methodsmv$methods( initialize=function(file="./matrixView.pdf",...){ viewerFile<<- file pdf(viewerFile) viewerDevice<<- dev.cur() dev.set(dev.prev()) callSuper(...)}, finalize=function(){ dev.off(viewerDevice)})## debugging an object: call browser() in method $edit()xx$trace(edit, browser)## debugging all objects from class mEdit in method $undo()mEdit$trace(undo, browser)## End(Not run)
Remove the method for a given function and signature. Obsolete forordinary applications: Method definitions in a package should neverneed to remove methods and it's very bad practice to remove methodsthat were defined in other packages.
removeMethod(f, signature, where)removeMethod(f, signature, where)
f,signature,where | As for |
TRUE if a methodwas found to be removed.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
These are old utility functions to construct, respectivelya list designed to represent the slots and superclasses anda list of prototype specifications. Therepresentation()function is no longer useful, since the argumentsslots andcontains tosetClass are now recommended.
Theprototype() function may still be used for thecorresponding argument, but asimple list of the same arguments works as well.
representation(...)prototype(...)representation(...)prototype(...)
... | The call to representation takes arguments that are single characterstrings. Unnamed arguments are classes that a newly defined classextends; named arguments name the explicit slots in the new class,and specify what class each slot should have. In the call to |
Therepresentation function applies tests for the validity ofthe arguments. Each must specify the name of a class.
The classes named don't have to exist whenrepresentation iscalled, but if they do, then the function will check for any duplicateslot names introduced by each of the inherited classes.
The arguments toprototype are usually named initial valuesfor slots, plus an optional first argument that gives the objectitself. The unnamed argument is typically useful if there is a datapart to the definition (see the examples below).
The value ofrepresentation is just the list of arguments, after these have been checkedfor validity.
The value ofprototype is the object to be used as theprototype. Slots will have been set consistently with thearguments, but the construction doesnot use the classdefinition to test validity of the contents (it hardly can, sincethe prototype object is usually supplied to create the definition).
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (For the R version.)
Chambers, John M. (1998)Programming with DataSpringer (For the original S4 version.)
## representation for a new class with a directly define slot "smooth"## which should be a "numeric" object, and extending class "track"representation("track", smooth ="numeric")### >>> This *is* old syntax -- use 'contains=*, slots=*' instead <<<### ========== ---------- ------ ======setClass("Character",representation("character"))setClass("TypedCharacter",representation("Character",type="character"), prototype(character(0),type="plain"))ttt <- new("TypedCharacter", "foo", type = "character")setClass("num1", representation(comment = "character"), contains = "numeric", prototype = prototype(pi, comment = "Start with pi"))## representation for a new class with a directly define slot "smooth"## which should be a "numeric" object, and extending class "track"representation("track", smooth="numeric")### >>> This *is* old syntax -- use 'contains=*, slots=*' instead <<<### ========== ---------- ------ ======setClass("Character",representation("character"))setClass("TypedCharacter",representation("Character",type="character"), prototype(character(0),type="plain"))ttt<- new("TypedCharacter","foo", type="character")setClass("num1", representation(comment="character"), contains="numeric", prototype= prototype(pi, comment="Start with pi"))
A regular (S4) class may contain an S3 class, if that class has been registered (by callingsetOldClass). The functions described here provideinformation about contained S3 classes. See the section ‘Functions’.
In modernR, these functions are notusually needed to program with objects from the S4 class. Standard computations work as expected, including method selectionfor both S4 and S3. To coerce an object to its contained S3 class,use either of the expressions:
as(object, S3Class); as(object, "S3")
whereS3Class evaluates to the name of the contained class. Thesereturn slightly different objects, which in rare cases may need tobe distinguished. See the section “Contained S3 Objects”.
S3Part(object, strictS3 = FALSE, S3Class)S3Class(object)isXS3Class(classDef)slotsFromS3(object)## the replacement versions of the functions are not recommended## Create a new object from the class or use the replacement version of as().S3Part(object, strictS3 = FALSE, needClass = ) <- valueS3Class(object) <- valueS3Part(object, strictS3=FALSE, S3Class)S3Class(object)isXS3Class(classDef)slotsFromS3(object)## the replacement versions of the functions are not recommended## Create a new object from the class or use the replacement version of as().S3Part(object, strictS3=FALSE, needClass=)<- valueS3Class(object)<- value
object | an object from some class that extends a registeredS3 class, or a basicvector, matrix or array object type. For most of the functions, an S3 object can also be supplied,with the interpretation that it is its own S3 part. |
strictS3 | If |
S3Class | the |
classDef | a class definition object, as returned by The remaining arguments apply only to the replacement versions,which are not recommended. |
needClass | Require that the replacement value be this class or asubclass of it. |
value | For For |
S3Part: Returns an object from the S3 class that appearedin thecontains= argument tosetClass.
If called withstrictS3 = TRUE,S3Part() constructs the underlyingS3 object by eliminatingall the formally defined slots and turning off the S4 bit of theobject. WithstrictS3 = FALSE the object returned is fromthe corresponding S4 class. For consistency and generality,S3Part() works also for classes that extend the basic vector,matrix and array classes.
A call to is equivalent coercing the object to class"S3" forthe strict case, or to whatever the specific S3 class was, for thenon-strict case. Theas() calls are usually easier forreaders to understand.
S3Class: Returns the character vector of S3 class(es) stored inthe object, if the class has the corresponding.S3Class slot.Currently, the function defaults toclass otherwise.
isXS3Class: ReturnsTRUE orFALSE accordingto whether the class defined byClassDefextends S3 classes (specifically, whether it has the slot forholding the S3 class).
slotsFromS3: returns a list of the relevant slot classes, or anempty list for any other object.
The functionslotsFromS3() is a generic function usedinternally to access the slots associated with the S3 part of theobject. Methods for this function are created automatically whensetOldClass is called with theS4Classargument. Usually, there is only one S3 slot, containing the S3class, but theS4Class argument may provide additional slots,in the case that the S3 class has some guaranteed attributes thatcan be used as formal S4 slots. See the corresponding section inthe documentation ofsetOldClass.
Registering an S3 class defines an S4 class. Objects from thisclass are essentially identical in content to an object from the S3class, except for two differences. The value returned byclass() will always be a single string for the S4object, andisS4() will returnTRUE orFALSE in the two cases. See the example below. It is barelypossible that some S3 code will not work with the S4 object; if so,useas(x, "S3").
Objects from a class that extends an S3 class will have some basic type andpossibly some attributes. For an S3 class that has an equivalent S4definition (e.g.,"data.frame"), an extending S4 class willhave a data part and slots. For other S3 classes (e.g.,"lm") anobject from the extending S4 class will be some sort of basic type,nearly always a vector type (e.g.,"list" for"lm"),but the data part will not have a formal definition.
Registering an S3 class by a call tosetOldClass creates a class of the same name with a slot".S3Class" to holdthe corresponding S3 vector of class strings.New S4 classes that extend suchclasses also have the same slot, set to the S3 class of thecontained S3object,which may be an(S3) subclass of the registered class.For example, an S4 class might contain the S3 class"lm", butan object from the class might contain an object from class"mlm", as in the"xlm"example below.
R is somewhat arbitrary about whatit treats as an S3 class:"ts" is, but"matrix" and"array"are not.For classes that extendthose, assuming they contain an S3 class is incorrect and will cause someconfusion—not usually disastrous, but the better strategyis to stick to the explicit “class”.Thusas(x, "matrix") rather thanas(x, "S3") orS3Part(x).
Objects inR have an internal bit that indicates whether or not totreat the object as coming from an S4 class. This bit is tested byisS4 and can be set on or off byasS4.The latter function, however, does no checking or interpretation;you should only use it if you are very certain every detail has beenhandled correctly.
As a friendlier alternative, methods have been defined for coercingto the virtual classes"S3" and"S4". The expressionsas(object, "S3") andas(object, "S4") return S3and S4 objects, respectively. In addition, they attemptto do conversions in a valid way, and also check validity whencoercing to S4.
The expressionas(object, "S3") can be used in two ways. Forobjects from one of the registered S3 classes, the expression willensure that the class attribute is the full multi-string S3 classimplied byclass(object). If the registered class has knownattribute/slots, these will also be provided.
Another use ofas(object, "S3") is to take an S4 object andturn it into an S3 object with corresponding attributes. This isonly meaningful with S4 classes that have a data part. If you wantto operate on the object without invoking S4 methods, thisconversion is usually the safest way.
The expressionas(object, "S4") will use the attributes inthe object to create an object from the S4 definition ofclass(object). This is a general mechanism to createpartially defined version of S4 objects via S3 computations (notmuch different from invokingnew with correspondingarguments, but usable in this form even if the S4 object has aninitialize method with different arguments).
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10, particularly Section 10.8)
## an "mlm" object, regressing two variables on two otherssepal <- as.matrix(datasets::iris[,c("Sepal.Width", "Sepal.Length")])fit <- lm(sepal ~ Petal.Length + Petal.Width + Species, data = datasets::iris)class(fit) # S3 class: "mlm", "lm"## a class that contains "mlm"myReg <- setClass("myReg", slots = c(title = "character"), contains = "mlm")fit2 <- myReg(fit, title = "Sepal Regression for iris data")fit2 # shows the inherited "mlm" object and the titleidentical(S3Part(fit2), as(fit2, "mlm"))class(as(fit2, "mlm")) # the S4 class, "mlm"class(as(fit2, "S3")) # the S3 class, c("mlm", "lm")## An object may contain an S3 class from a subclass of that declared:xlm <- setClass("xlm", slots = c(eps = "numeric"), contains = "lm")xfit <- xlm(fit, eps = .Machine$double.eps)[email protected] # c("mlm", lm")## an "mlm" object, regressing two variables on two otherssepal<- as.matrix(datasets::iris[,c("Sepal.Width","Sepal.Length")])fit<- lm(sepal~ Petal.Length+ Petal.Width+ Species, data= datasets::iris)class(fit)# S3 class: "mlm", "lm"## a class that contains "mlm"myReg<- setClass("myReg", slots= c(title="character"), contains="mlm")fit2<- myReg(fit, title="Sepal Regression for iris data")fit2# shows the inherited "mlm" object and the titleidentical(S3Part(fit2), as(fit2,"mlm"))class(as(fit2,"mlm"))# the S4 class, "mlm"class(as(fit2,"S3"))# the S3 class, c("mlm", "lm")## An object may contain an S3 class from a subclass of that declared:xlm<- setClass("xlm", slots= c(eps="numeric"), contains="lm")xfit<- xlm(fit, eps= .Machine$double.eps)xfit@.S3Class# c("mlm", lm")
Methods can be defined forgroup generic functions. Each groupgeneric function has a number ofmember generic functionsassociated with it.
Methods defined for a group generic function cause the samemethod to be defined for each member of the group, but a method explicitlydefined for a member of the group takes precedence over amethod defined, with the same signature, for the group generic.
The functions shown in this documentation page all reside in themethods package, but the mechanism is available to anyprogrammer, by callingsetGroupGeneric (provided packagemethods is attached).
## S4 group generics:Arith(e1, e2)Compare(e1, e2)Ops(e1, e2)matrixOps(x, y)Logic(e1, e2)Math(x)Math2(x, digits)Summary(x, ..., na.rm = FALSE)Complex(z)## S4 group generics:Arith(e1, e2)Compare(e1, e2)Ops(e1, e2)matrixOps(x, y)Logic(e1, e2)Math(x)Math2(x, digits)Summary(x,..., na.rm=FALSE)Complex(z)
x,y,z,e1,e2 | objects. |
digits | number of digits to be used in |
... | further arguments passed to or from methods. |
na.rm | logical: should missing values be removed? |
Methods can be defined for the group generic functions by calls tosetMethod in the usual way.Note that the group generic functionsshould never be called directly– a suitable error message will result if they are. When metadatafor a group generic is loaded, the methods defined become methodsfor the members of the group, but only if no method has beenspecified directly for the member function for the same signature.The effect is that group generic definitions are selected beforeinherited methods but after directly specified methods. For more onmethod selection, seeMethods_Details.
There are alsoS3 groupsMath,Ops,Summary,ComplexandmatrixOps, see?S3groupGeneric,with no correspondingR objects, but these are irrelevant for S4group generic functions.
The members of the group defined by a particular generic can beobtained by callinggetGroupMembers. For the groupgeneric functions currently defined in this package the members areas follows:
Arith"+","-","*","^","%%","%/%","/"
Compare"==",">","<","!=","<=",">="
Logic"&","|".
Ops"Arith","Compare","Logic"
Math"abs","sign","sqrt","ceiling","floor","trunc","cummax","cummin","cumprod","cumsum","log","log10","log2","log1p","acos","acosh","asin","asinh","atan","atanh","exp","expm1","cos","cosh","cospi","sin","sinh","sinpi","tan","tanh","tanpi","gamma","lgamma","digamma","trigamma"
Math2"round","signif"
Summary"max","min","range","prod","sum","any","all"
Complex"Arg","Conj","Im","Mod","Re"
matrixOps"%*%"
Note thatOps merely consists of three sub groups.
All the functions in these groups (other than the group genericsthemselves) are basic functions inR. They are not by default S4 genericfunctions, and many of them are defined as primitives. However, you can still defineformal methods for them, both individually and via the group generics. It all works more or less as youmight expect, admittedly via a bit of trickery in the background.SeeMethods_Details for details.
Note that two members of theMath group,log andtrunc, have ... as an extra formal argument.Since methods forMath will have only one formal argument,you must set a specific method for these functions in order to callthem with the extra argument(s).
For further details about group generic functions see section 10.5 ofthe second reference.
Chambers, John M. (2016)Extending R,Chapman & Hall. (Chapters 9 and 10.)
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (Section 10.5)
The functioncallGeneric is nearly alwaysrelevant when writing a method for a group generic. See theexamples below and in section 10.5 ofSoftware for Data Analysis.
SeeS3groupGeneric for S3 group generics.
setClass("testComplex", slots = c(zz = "complex"))## method for whole group "Complex"getGroupMembers("Complex") # "Arg" "Conj" "Im" "Mod" "Re" setMethod("Complex", "testComplex", function(z) c("groupMethod", callGeneric(z@zz)))## exception for Arg() :setMethod("Arg", "testComplex", function(z) c("ArgMethod", Arg(z@zz)))z1 <- 1+2iz2 <- new("testComplex", zz = z1)stopifnot(identical(Mod(z2), c("groupMethod", Mod(z1))))stopifnot(identical(Arg(z2), c("ArgMethod", Arg(z1))))selectMethod("Re", signature = "testComplex") # shows Generic: .. "Re" & .."Complex"setClass("testComplex", slots= c(zz="complex"))## method for whole group "Complex"getGroupMembers("Complex")# "Arg" "Conj" "Im" "Mod" "Re"setMethod("Complex","testComplex",function(z) c("groupMethod", callGeneric(z@zz)))## exception for Arg() :setMethod("Arg","testComplex",function(z) c("ArgMethod", Arg(z@zz)))z1<-1+2iz2<- new("testComplex", zz= z1)stopifnot(identical(Mod(z2), c("groupMethod", Mod(z1))))stopifnot(identical(Arg(z2), c("ArgMethod", Arg(z1))))selectMethod("Re", signature="testComplex")# shows Generic: .. "Re" & .."Complex"
An object from this class represents a single ‘is’relationship; lists of these objects are used to represent all theextensions (superclasses) and subclasses for a given class. Theobject contains information about how the relation is defined andmethods to coerce, test, and replace correspondingly.
Objects from this class are generated bysetIs,from direct calls and from thecontains= information in a call tosetClass, and from class unions created bysetClassUnion.In the last case, the information is stored in defining thesubclasses of the union class (allowing unions to contain sealed classes).
subClass,superClass:The classes beingextended: corresponding to thefrom, andtoarguments tosetIs.
package:The package to which that class belongs.
coerce:A function to carry out the as() computationimplied by the relation. Note that these functions shouldnot be used directly. They only deal with thestrict=TRUE calls to theas function, withthe full method constructed from this mechanically.
test:The function that would test whether therelation holds. Except for explicitly specifiedtestarguments tosetIs, this function is trivial.
replace:The method used to implementas(x, Class) <- value.
simple:A"logical" flag,TRUE if thisis a simple relation, either because one class is contained in thedefinition of another, or because a class has been explicitlystated to extend a virtual class. For simple extensions, thethree methods are generated automatically.
by:If this relation has been constructedtransitively, the first intermediate class from the subclass.
dataPart:A"logical" flag,TRUE ifthe extended class is in fact the data part of the subclass. Inthis case the extended class is a basic class (i.e., a type).
distance:The distance between the two classes,1 for directly contained classes, plus the number of generations between otherwise.
No methods defined with class"SClassExtension" in thesignature.
is,as, and theclassRepresentation class.
Return superclasses ofClassDef, possibly only non-virtual ordirect or simple ones.
These functions are designed to be fast, and consequently only workwith thecontains slot of the corresponding class definitions.
selectSuperClasses(Class, dropVirtual = FALSE, namesOnly = TRUE, directOnly = TRUE, simpleOnly = directOnly, where = topenv(parent.frame())).selectSuperClasses(ext, dropVirtual = FALSE, namesOnly = TRUE, directOnly = TRUE, simpleOnly = directOnly)selectSuperClasses(Class, dropVirtual=FALSE, namesOnly=TRUE, directOnly=TRUE, simpleOnly= directOnly, where= topenv(parent.frame())).selectSuperClasses(ext, dropVirtual=FALSE, namesOnly=TRUE, directOnly=TRUE, simpleOnly= directOnly)
Class | name of the class or (more efficiently) the classdefinition object (see |
dropVirtual | logical indicating if only non-virtual superclassesshould be returned. |
namesOnly | logical indicating if only a vector names instead ofa named list class-extensions should be returned. |
directOnly | logical indicating if only adirect superclasses should be returned. |
simpleOnly | logical indicating if only simple class extensionsshould be returned. |
where | (only used when |
ext | for |
acharacter vector (ifnamesOnly is true, as perdefault) or a list of class extensions (as thecontains slot inthe result ofgetClass).
The typical user level function isselectSuperClasses()which calls.selectSuperClasses(); i.e., the latter should onlybe used for efficiency reasons by experienced useRs.
is,getClass; further, the more technicalclassclassRepresentation documentation.
setClass("Root")setClass("Base", contains = "Root", slots = c(length = "integer"))setClass("A", contains = "Base", slots = c(x = "numeric"))setClass("B", contains = "Base", slots = c(y = "character"))setClass("C", contains = c("A", "B"))extends("C") #--> "C" "A" "B" "Base" "Root"selectSuperClasses("C") # "A" "B"selectSuperClasses("C", directOnly=FALSE) # "A" "B" "Base" "Root"selectSuperClasses("C", dropVirtual=TRUE, directOnly=FALSE)# ditto w/o "Root"setClass("Root")setClass("Base", contains="Root", slots= c(length="integer"))setClass("A", contains="Base", slots= c(x="numeric"))setClass("B", contains="Base", slots= c(y="character"))setClass("C", contains= c("A","B"))extends("C")#--> "C" "A" "B" "Base" "Root"selectSuperClasses("C")# "A" "B"selectSuperClasses("C", directOnly=FALSE)# "A" "B" "Base" "Root"selectSuperClasses("C", dropVirtual=TRUE, directOnly=FALSE)# ditto w/o "Root"
A call tosetAs defines a method for coercing an object ofclassfrom to classto. The methods will then be usedby calls toas for objects with classfrom,including calls that replace part of the object.
Methods for this purpose work indirectly, by defining methods forfunctioncoerce. Thecoerce function isnot tobe called directly, and method selection uses class inheritance onlyon the first argument.
setAs(from, to, def, replace, where = topenv(parent.frame()))setAs(from, to, def, replace, where= topenv(parent.frame()))
from,to | The classes between which the coerce methods |
def | function of one argument. It will get an object fromclass |
replace | if supplied, the function to use as a replacementmethod, when The remaining argument will not be used in standard applications. |
where | the position or environment in which to store theresulting methods. Do not use this argument when defining a methodin a package. Only the default, the namespace of the package,should be used in normal situations. |
Objects from one class can turn into objects from another classeither automatically or by an explicit call to theasfunction. Automatic conversion is special, and comes from thedesigner of one class of objects asserting that this class extendsanother class. The most common case is that one or more class namesare supplied in thecontains= argument tosetClass, inwhich case the new class extends each of the earlier classes (in theusual terminology, the earlier classes aresuperclasses ofthe new class and it is asubclass of each of them).
This form of inheritance is calledsimple inheritance inR.SeesetClass for details.Inheritance can also be defined explicitly by a call tosetIs.The two versions have slightly different implications for coerce methods.Simple inheritance implies that inherited slots behave identically in the subclass and the superclass.Whenever two classes are related by simple inheritance, corresponding coerce methodsare defined for both direct and replacement use ofas.In the case of simple inheritance, these methods do the obviouscomputation: they extract or replace the slots in the object thatcorrespond to those in the superclass definition.
The implicitly defined coerce methods may be overridden by a calltosetAs; note, however, that the implicit methods are defined for eachsubclass-superclass pair, so that you must override each of theseexplicitly, not rely on inheritance.
When inheritance is defined by a call tosetIs, the coerce methods are provided explicitly, not generated automatically.Inheritance will apply (to thefrom argument, as described in the section below).You could also supply methods viasetAs for non-inherited relationships, and now these also can be inherited.
For further on the distinction between simple and explicit inheritance, seesetIs.
as andsetAs WorkThe functionas turnsobject into an objectof classClass. In doing so, it applies a “coercemethod”, using S4classes and methods, but in a somewhat special way.Coerce methods are methods for the functioncoerce or, in thereplacement case the function`coerce<-`.These functions have two arguments in method signatures,fromandto, corresponding to the class of the object and thedesired coerce class.These functions must not be called directly, but are used to storetables of methods for the use ofas, directly and forreplacements.In this section we will describe the direct case, but except wherenoted the replacement case works the same way, using`coerce<-`and thereplace argument tosetAs, rather thancoerce and thedef argument.
Assuming theobject is not already of the desired class,as first looks for a method in the table of methodsfor the functioncoerce for the signaturec(from = class(object), to = Class), in the same way method selection would do its initial lookup.To be precise, this means the table of both direct and inheritedmethods, but inheritance is used specially in this case (see below).
If no method is found,as looks for one.First, if eitherClass orclass(object) is a superclassof the other, the class definition will contain the information neededto construct a coerce method.In the usual case that the subclass contains the superclass (i.e., hasall its slots), the method is constructed either by extracting orreplacing the inherited slots.Non-simple extensions (the result of a call tosetIs)will usually contain explicit methods, though possibly not for replacement.
If no subclass/superclass relationship provides a method,aslooks for an inherited method, but applying, inheritance for the argumentfrom only, not forthe argumentto (if you think about it, you'll probably agreethat you wouldn't want the result to be from some class other than theClass specified). Thus,selectMethod("coerce", sig, useInherited= c(from=TRUE, to= FALSE))replicates the method selection used byas().
In nearly all cases the method found in this way will be cached in thetable of coerce methods (the exception being subclass relationships with a test, whichare legal but discouraged).So the detailed calculations should be done only on the firstoccurrence of a coerce fromclass(object) toClass.
Note thatcoerce is not a standard generic function. It isnot intended to be called directly. To prevent accidentally cachingan invalid inherited method, calls are routed to an equivalent call toas, and a warning is issued. Also, calls toselectMethod for this function may not represent themethod thatas will choose. You can only trust the result ifthe corresponding call toas has occurred previously in thissession.
With this explanation as background, the functionsetAs does afairly obvious computation: It constructs and sets a method for the functioncoerce with signaturec(from, to), using thedefargument to define the body of the method. The function supplied asdef can have one argument (interpreted as an object to becoerced) or two arguments (thefrom object and thetoclass). Either way,setAs constructs a function of twoarguments, with the second defaulting to the name of thetoclass. The method will be called fromas with the objectas thefrom argument and noto argument, with the default for this argument being the name of the intendedto class, so the method can use this information in messages.
The direct version of theas function also has astrict= argument that defaults toTRUE.Calls during the evaluation of methods for other functions will set this argument toFALSE.The distinction is relevant when the object being coerced is from a simple subclass of theto class; ifstrict=FALSE in this case, nothing need be done.For most user-written coerce methods, when the two classes have no subclass/superclass, thestrict= argument is irrelevant.
Thereplace argument tosetAs provides a method for`coerce<-`.As with all replacement methods, the last argument of the method musthave the namevalue for the object on the right of theassignment.As with thecoerce method, the first two arguments arefrom, to; there is nostrict= option for the replace case.
The functioncoerce exists as a repository forsuch methods, to be selected as described above by theasfunction. Actually dispatching the methods usingstandardGeneric could produce incorrect inherited methods, by usinginheritance on theto argument; as mentioned, this is not the logic used foras.To prevent selecting and caching invalid methods, calls tocoerce arecurrently mapped into calls toas, with a warning message.
Methods are pre-defined for coercing any object to one of the basicdatatypes. For example,as(x, "numeric") uses the existingas.numeric function. These built-in methods can be listed byshowMethods("coerce").
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
If you think of usingtry(as(x, cl)), considercanCoerce(x, cl) instead.
## using the definition of class "track" from \link{setClass}setAs("track", "numeric", function(from) from@y)t1 <- new("track", x=1:20, y=(1:20)^2)as(t1, "numeric")## The next example shows:## 1. A virtual class to define setAs for several classes at once.## 2. as() using inherited informationsetClass("ca", slots = c(a = "character", id = "numeric"))setClass("cb", slots = c(b = "character", id = "numeric"))setClass("id")setIs("ca", "id")setIs("cb", "id")setAs("id", "numeric", function(from) from@id)CA <- new("ca", a = "A", id = 1)CB <- new("cb", b = "B", id = 2)setAs("cb", "ca", function(from, to )new(to, a=from@b, id = from@id))as(CB, "numeric")## using the definition of class "track" from \link{setClass}setAs("track","numeric",function(from) from@y)t1<- new("track", x=1:20, y=(1:20)^2)as(t1,"numeric")## The next example shows:## 1. A virtual class to define setAs for several classes at once.## 2. as() using inherited informationsetClass("ca", slots= c(a="character", id="numeric"))setClass("cb", slots= c(b="character", id="numeric"))setClass("id")setIs("ca","id")setIs("cb","id")setAs("id","numeric",function(from) from@id)CA<- new("ca", a="A", id=1)CB<- new("cb", b="B", id=2)setAs("cb","ca",function(from, to)new(to, a=from@b, id= from@id))as(CB,"numeric")
Create a class definition and return a generator function to createobjects from the class. Typical usage will beof the style:
myClass <- setClass("myClass", slots= ...., contains =....)
where the first argument is the name of the new class and, if supplied, the argumentsslots= andcontains= specify the slotsin the new class and existing classes from which the new classshould inherit. Calls tosetClass() are normally found in thesource of a package; when the package is loaded the class will bedefined in the package's namespace. Assigning the generatorfunction with the name of the class is convenient for users, butnot a requirement.
setClass(Class, representation, prototype, contains=character(), validity, access, where, version, sealed, package, S3methods = FALSE, slots)setClass(Class, representation, prototype, contains=character(), validity, access, where, version, sealed, package, S3methods=FALSE, slots)
Class | character string name for the class. |
slots | The names and classes for the slots in the new class. This argumentmust be supplied by name, The argument must be vector with a names attribute, the names being those of the slots inthe new class. Each element of the vector specifies anexisting class; the corresponding slot must be from this classor a subclass of it. Usually, this is a character vectornaming the classes. It's also legal for the elements of thevector to be class representation objects, as returned by As a limitingcase, the argument may be an unnamed charactervector; the elements are taken as slot names and all slots havethe unrestricted class |
contains | A vector specifying existing classes from whichthis class should inherit. The new class will have all the slotsof the superclasses, with the same requirements on the classesof these slots. This argumentmust be supplied by name, See the section ‘Virtual Classes’ for the specialsuperclass |
prototype,where,validity,sealed,package | These arguments are currently allowed, but either they are unlikely to beuseful or there are modern alternatives that are preferred.
|
representation,access,version,S3methods | All thesearguments are deprecated from version 3.0.0 ofR and should beavoided.
|
A generator function suitable for creating objects from the class isreturned, invisibly. A call to this function generates a call tonew for the class. The call takes any number of arguments,which will be passed on to the initialize method. If noinitialize method is defined for the class or one of itssuperclasses, the default method expects named arguments with thename of one of the slots and unnamed arguments that are objects fromone of the contained classes.
Typically the generator function is assigned the name of the class,for programming clarity. This is not a requirement and objectsfrom the class can also be generated directly fromnew. The advantages of the generator function are aslightly simpler and clearer call, and that the call will containthe package name of the class (eliminating any ambiguity if twoclasses from different packages have the same name).
If the class is virtual, an attempt to generate an object fromeither the generator ornew()will result in an error.
The two essential arguments other than the class name areslots andcontains, defining the explicit slotsand the inheritance (superclasses). Together, these arguments defineall the information in an object from this class; that is, the namesof all the slots and the classes required for each of them.
The name of the class determineswhich methods apply directly to objects from this class. Thesuperclass information specifies which methods apply indirectly,through inheritance. SeeMethods_Details for inheritance in methodselection.
The slots in a class definition will be the union of all the slotsspecified directly byslots and all the slots in allthe contained classes.There can only be one slot with a given name.A class may override the definition of a slot with a given name, butonly if the newly specified class is a subclass of theinherited one.For example, if the contained class had a slota with class"ANY", then a subclass could specifya with class"numeric",but if the original specification for the slot was class"character", the new call tosetClass would generate an error.
Slot names"class" and"Class" are not allowed.There are other slot names with a special meaning; these names start withthe"." character. To be safe, you should define all ofyour own slots with names starting with an alphabetic character.
Some inherited classes will be treated specially—object types, S3classes and a few special cases—whether inheriteddirectly or indirectly. See the next three sections.
Classes exist for which no actual objects can be created, thevirtual classes.
The most common and useful form of virtual class is theclassunion, a virtual class that is defined in a call tosetClassUnion() rather than a call tosetClass().This call lists themembers of the union—subclassesthat extend the new class.Methods that are written with the class union in the signatureare eligible for use with objects from any of the member classes.Classunions can include as members classes whosedefinition is otherwise sealed, including basicR data types.
Calls tosetClass() will also create a virtual class,either when only theClass argument is supplied (no slotsor superclasses) or when thecontains= argument includesthe special class name"VIRTUAL".
In the latter case, avirtual class may includeslots to provide some common behavior without fully definingthe object—see the classtraceable for anexample.Note that"VIRTUAL" does not carry over to subclasses; aclass that contains a virtual class is not itself automatically virtual.
In addition to containing other S4 classes, a class definition cancontain either an S3 class (see the next section) or a built-in R pseudo-class—oneof theRobject types or one of the specialR pseudo-classes"matrix" and"array".A class can contain at most one of the object types, directly or indirectly.When it does, that contained class determines the “data part”of the class.This appears as a pseudo-slot,".Data" and can be treated as aslot but actually determinesthe type of objects from this slot.
Objects from the new class try to inherit the built inbehavior of the contained type.In the case of normalR data types, including vectors, functions andexpressions, the implementation is relatively straightforward.For any objectx from the class,typeof(x) will be the contained basic type; and a specialpseudo-slot,.Data, will be shown with the corresponding class.See the"numWithId" example below.
Classes may also inherit from"vector","matrix" or"array".The data part of these objects can be any vector data type.
For an object from any class that doesnot contain one of thesetypes or classes,typeof(x) will be"S4".
SomeR data types do not behave normally, in the sense that they arenon-local references or other objects that are not duplicated.Examples include those corresponding to classes"environment","externalptr", and"name".These can not be the types for objects with user-definedclasses (either S4 or S3) because setting an attribute overwrites theobject in all contexts.It is possible to define a class that inherits from such types,through an indirect mechanism that stores the inherited object in areserved slot,".xData".See theexample for class"stampedEnv" below.An object from such a class doesnot have a".Data" pseudo-slot.
For most computations, these classes behave transparently as if theyinherited directly from the anomalous type.S3 method dispatch and the relevantas.type()functions should behave correctly, but code that uses the type of theobject directly will not.For example,as.environment(e1) would work as expected with the"stampedEnv" class, buttypeof(e1) is"S4".
Old-style S3 classes have no formal definition. Objects are“from” the class when their class attribute contains thecharacter string considered to be the class name.
Using such classes with formal classes and methods is necessarily arisky business, since there are no guarantees about the content of theobjects or about consistency of inherited methods.Given that, it is still possible to define a class that inherits froman S3 class, providing that class has been registered as an old class(seesetOldClass).
Broadly speaking, both S3 and S4 method dispatch try to behavesensibly with respect to inheritance in either system.Given an S4 object, S3 method dispatch and theinheritsfunction should use the S4 inheritance information.Given an S3 object, an S4 generic function will dispatch S4 methodsusing the S3 inheritance, provided that inheritance has been declared viasetOldClass. For details, seesetOldClassand Section 10.8 of the reference.
Class definitions normally belong to packages (but can be defined inthe global environment as well, by evaluating the expression on thecommand line or in a file sourced from the command line).The corresponding package name is part of the class definition; thatis, part of theclassRepresentation object holding thatdefinition. Thus, two classes with the same name can exist indifferent packages, for most purposes.
When a class name is supplied for a slot or a superclass in a call tosetClass, acorresponding class definition will be found, looking from thenamespace of the current package, assuming the call in question appears directly in the source for thepackage, as it should to avoid ambiguity.The class definitionmust be already defined in this package, in the imports directives ofthe package'sDESCRIPTION andNAMESPACE files or in the basic classes defined by the methods package.(The ‘methods’ package must be included in the imports directivesfor any package that usesS4 methods and classes, to satisfy the"CMD check" utility.)
If a package imports two classes of the same name from separate packages, thepackageSlotof thename argument needs to be set to the package name of theparticular class.This should be a rare occurrence.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Classes_Details for a general discussion of classes,Methods_Details for an analogous discussion of methods,makeClassRepresentation
## A simple class with two slotstrack <- setClass("track", slots = c(x="numeric", y="numeric"))## an object from the classt1 <- track(x = 1:10, y = 1:10 + rnorm(10))## A class extending the previous, adding one more slottrackCurve <- setClass("trackCurve",slots = c(smooth = "numeric"),contains = "track")## an object containing a superclass objectt1s <- trackCurve(t1, smooth = 1:10)## A class similar to "trackCurve", but with different structure## allowing matrices for the "y" and "smooth" slotssetClass("trackMultiCurve", slots = c(x="numeric", y="matrix", smooth="matrix"), prototype = list(x=numeric(), y=matrix(0,0,0), smooth= matrix(0,0,0)))## A class that extends the built-in data type "numeric"numWithId <- setClass("numWithId", slots = c(id = "character"), contains = "numeric")numWithId(1:3, id = "An Example")## inherit from reference object of type "environment"stampedEnv <- setClass("stampedEnv", contains = "environment", slots = c(update = "POSIXct"))setMethod("[[<-", c("stampedEnv", "character", "missing"), function(x, i, j, ..., value) { ev <- as(x, "environment") ev[[i]] <- value #update the object in the environment x@update <- Sys.time() # and the update time x})e1 <- stampedEnv(update = Sys.time())e1[["noise"]] <- rnorm(10)## A simple class with two slotstrack<- setClass("track", slots= c(x="numeric", y="numeric"))## an object from the classt1<- track(x=1:10, y=1:10+ rnorm(10))## A class extending the previous, adding one more slottrackCurve<- setClass("trackCurve",slots= c(smooth="numeric"),contains="track")## an object containing a superclass objectt1s<- trackCurve(t1, smooth=1:10)## A class similar to "trackCurve", but with different structure## allowing matrices for the "y" and "smooth" slotssetClass("trackMultiCurve", slots= c(x="numeric", y="matrix", smooth="matrix"), prototype= list(x=numeric(), y=matrix(0,0,0), smooth= matrix(0,0,0)))## A class that extends the built-in data type "numeric"numWithId<- setClass("numWithId", slots= c(id="character"), contains="numeric")numWithId(1:3, id="An Example")## inherit from reference object of type "environment"stampedEnv<- setClass("stampedEnv", contains="environment", slots= c(update="POSIXct"))setMethod("[[<-", c("stampedEnv","character","missing"),function(x, i, j,..., value){ ev<- as(x,"environment") ev[[i]]<- value#update the object in the environment x@update<- Sys.time()# and the update time x})e1<- stampedEnv(update= Sys.time())e1[["noise"]]<- rnorm(10)
A class may be defined as theunion of other classes; thatis, as a virtual class defined as a superclass of several otherclasses. Class unions are useful in method signatures or as slots inother classes, when we want to allow one of several classes to be supplied.
setClassUnion(name, members, where)isClassUnion(Class)setClassUnion(name, members, where)isClassUnion(Class)
name | the name for the new union class. |
members | the names of the classes that should be members of this union. |
where | where to save the new class definition. In calls froma package's source code, should be omitted to save the definitionin the package's namespace. |
Class | the name or definition of a class. |
The classes inmembers must be defined before creating theunion. However, members can be added later on to an existingunion, as shown in the example below. Class unions can bemembers of other class unions.
Class unions are the only way to create a new superclass ofa class whose definition is sealed. The namespace of allpackages is sealed when the package is loaded, protecting theclass and other definitions from being overwritten from anotherclass or from the global environment. A call tosetIs that tried to define a new superclass forclass"numeric", for example, would cause an error.
Class unions are the exception; the class union"maybeNumber" in the examples defines itself as a newsuperclass of"numeric". Technically, it does not alter themetadata object in the other package's namespace and, of course,the effect of the class union depends on loading the package itbelongs to. But, basically, class unions are sufficiently usefulto justify the exemption.
The different behavior for class unions is made possible because theclass definition object for class unions has itself a special class,"ClassUnionRepresentation", an extension of classclassRepresentation.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
## a class for either numeric or logical datasetClassUnion("maybeNumber", c("numeric", "logical"))## use the union as the data part of another classsetClass("withId", contains = "maybeNumber", slots = c(id = "character"))w1 <- new("withId", 1:10, id = "test 1")w2 <- new("withId", sqrt(w1)%%1 < .01, id = "Perfect squares")## add class "complex" to the union "maybeNumber"setIs("complex", "maybeNumber")w3 <- new("withId", complex(real = 1:10, imaginary = sqrt(1:10)))## a class union containing the existing class union "OptionalFunction"setClassUnion("maybeCode", c("expression", "language", "OptionalFunction"))is(quote(sqrt(1:10)), "maybeCode") ## TRUE## a class for either numeric or logical datasetClassUnion("maybeNumber", c("numeric","logical"))## use the union as the data part of another classsetClass("withId", contains="maybeNumber", slots= c(id="character"))w1<- new("withId",1:10, id="test 1")w2<- new("withId", sqrt(w1)%%1<.01, id="Perfect squares")## add class "complex" to the union "maybeNumber"setIs("complex","maybeNumber")w3<- new("withId", complex(real=1:10, imaginary= sqrt(1:10)))## a class union containing the existing class union "OptionalFunction"setClassUnion("maybeCode", c("expression","language","OptionalFunction"))is(quote(sqrt(1:10)),"maybeCode")## TRUE
Create a generic version of the named function so that methods maybe defined for it. A call tosetMethod will callsetGeneric automatically if applied to a non-genericfunction.
An explicit call tosetGeneric is usually not required, butdoesn't hurt and makes explicit that methods are being defined for anon-generic function.
Standard calls will be of the form:
setGeneric(name)
wherename specifies an existing function, possibly in anotherpackage. An alternative when creating a new generic function in this package is:
setGeneric(name, def)
where the function definitiondef specifies the formalarguments and becomes the default method.
setGeneric(name, def= , group=list(), valueClass=character(), where= , package= , signature= , useAsDefault= , genericFunction= , simpleInheritanceOnly = )setGeneric(name, def=, group=list(), valueClass=character(), where=, package=, signature=, useAsDefault=, genericFunction=, simpleInheritanceOnly=)
name | The character string name of the generic function. |
def | An optional function object, defining the non-genericversion, to become the default method. This is equivalent ineffect to assigning The following arguments are specialized, optionally usedwhen creating a new generic function with non-standardfeatures. They should not be used when the non-generic is inanother package. |
group | The name of the groupgeneric function to which this function belongs. SeeMethods_Details for details of group generic functions in methodselection andS4groupGeneric for existing groups. |
valueClass | A character vector specifying one or more classnames. The value returned by the generic function musthave (or extend) this class, or one of the classes; otherwise,an error is generated. |
signature | The vector of names from among the formal arguments tothe function, that will be allowed in the signature of methods for thisfunction, in calls to A non-standard signature for the generic function may beused to exclude arguments that take advantage of lazy evaluation;in particular, if the argument maynot be evaluated then itcannot be part of the signature. While It's usually a mistake to omit arguments from the signature in thebelief that this improves efficiency. For method selection, thearguments that are used in the signatures for themethodsare what counts, and then only seriously on the first call to thefunction with that combination of classes. |
simpleInheritanceOnly | Supply this argument as |
useAsDefault | Override the usual default method mechanism. Only relevant whendefining a nonstandard generic function.See the section ‘Specialized Local Generics’. The remaining arguments are obsolete for normal applications. |
package | The name of the package with which this function isassociated. Should be determined automatically from thenon-generic version. |
where | Where to store the resulting objects as side effects.The default, to store in the package's namespace, is the onlysafe choice. |
genericFunction | Obsolete. |
ThesetGeneric function exists for its side effect: saving thegeneric function to allow methods to be specified later. It returnsname.
ThesetGeneric function is called to initialize a genericfunction as preparation for defining some methods for that function.
The simplest and most common situation is thatname specifiesan existing function, usually in another package. You now want todefine methods for this function. In this case you shouldsupply onlyname, for example:
setGeneric("colSums")
There must be an existing function of this name (in this case inpackage"base"). The non-generic function can be in the samepackage as the call, typically the case when you are creating a newfunction plus methods for it. When the function is inanother package, it must be available by name, forexample through animportFrom() directive in this package'sNAMESPACE file. Not required for functions in"base",which are implicitly imported.
A generic version ofthe function will be created in the current package. The existing functionbecomes the default method, and the package slot of the new genericfunction is set to the location of the original function("base" in the example).
Two special types of non-generic should be noted.Functions that dispatch S3 methods by callingUseMethod are ordinary functions, not objects from the"genericFunction" class. They are made generic like anyother function, but some special considerations apply to ensure thatS4 and S3 method dispatch is consistent (seeMethods_for_S3).
Primitive functions are handled in C code and don't exist as normalfunctions.A call tosetGeneric is allowed in the simple form, but noactual generic function object is created. Method dispatch willtake place in the C code. See the section on Primitive Functions formore details.
It's an important feature that theidentical generic function definition is created in every package thatuses the samesetGeneric() call.When any of these packages is loaded into anR session, thisfunction will be added to a table of generic functions, and willcontain a methods table of all the available methods for thefunction.
CallingsetGeneric() is not strictlynecessary before callingsetMethod(). Ifthe function specified in the call tosetMethod is not generic,setMethod will execute the call tosetGeneric itself.In the case that the non-generic is in another package, does notdispatch S3 methods and is not a primitive, a message is printed noting thecreation of the generic function the first timesetMethod is called.
The second common use ofsetGeneric() is to create a newgeneric function, unrelated to any existing function. See theasRObject() example below.This case can be handled just like the previous examples, with onlythe difference that the non-generic function exists in thecurrent package.Again, the non-generic version becomes the default method.For clarity it's best for the assignment to immediately precede thecall tosetGeneric() in the source code.
Exactly the same result can be obtained by supplying the default asthedef argument instead of assigning it.In some applications, there will be no completely general defaultmethod. While there is a special mechanism for this (see the‘Specialized Local Generics’ section), the recommendation is to provide adefault method that signals an error, but with a message thatexplains as clearly as you can why a non-default method is needed.
The great majority of calls tosetGeneric() should eitherhave one argument to ensure that an existing function can havemethods, or argumentsname anddef to create a newgeneric function and optionally a default method.
It is possible to create generic functions with nonstandardsignatures, or functions that do additional computations besidesmethod dispatch or that belong to a group of generic functions.
None of these mechanisms should be used with a non-generic functionfrom adifferent package, because the result is to create ageneric function that may not be consistent from one package to another.When any such options are used,the new generic function will be assigned with apackage slot set to thecurrent package, not the one in whichthe non-generic version of the function is found.
There is a mechanism to define a specialized generic version of anon-generic function, theimplicitGenericconstruction.This defines the generic version, but then reverts the function toit non-generic form, saving the implicit generic in a table to beactivated when methods are defined.However, the mechanism can only legitimately be used either for a non-genericin the same package or by the"methods" package itself.And in the first case, there is no compelling reason not to simplymake the function generic, with the non-generic as the defaultmethod.SeeimplicitGeneric for details.
The body of a generic function usually does nothing except fordispatching methods by a call tostandardGeneric. Under somecircumstances you might just want to do some additional computation inthe generic function itself. As long as your function eventuallycallsstandardGeneric that is permissible.See the example"authorNames" below.
In this case, thedef argument will define the nonstandardgeneric, not the default method.An existing non-generic of the same name and calling sequence shouldbe pre-assigned. It will become the default method, as usual.(An alternative is theuseAsDefault argument.)
By default, the generic function can return any object. IfvalueClass is supplied, it should be a vector of class names;the value returned by a method is then required to satisfyis(object, Class) for one of the specified classes. An empty(i.e., zero length) vector of classes means anything is allowed. Notethat more complicated requirements on the result can be specifiedexplicitly, by defining a non-standard generic function.
If thedef argument callsstandardGeneric() (with orwithout additional computations) and there is no existingnon-generic version of the function, the generic is created withouta default method. This is not usually a good idea: better to have adefault method that signals an error with a message explaining whythe default case is not defined.
A new generic function can be created belonging to an existing groupby including thegroup argument. The argument list of thenew generic must agree with that of the group. SeesetGroupGeneric for defining a new group generic.For the role of group generics indispatching methods, seeGroupGenericFunctions and section10.5 of the second reference.
A number of the basicR functions are specially implemented asprimitive functions, to be evaluated directly in the underlying C coderather than by evaluating anR language definition. Most haveimplicit generics (seeimplicitGeneric), and becomegeneric as soon as methods (including group methods) are defined onthem. Others cannot be made generic.
CallingsetGeneric() forthe primitive functions in the base package differs in that it does not, in fact,generate an explicit generic function.Methods for primitives are selected and dispatched fromthe internal C code, to satisfy concerns for efficiency.The same is true for a fewnon-primitive functions that dispatch internally. These includeunlist andas.vector.
Note, that the implementation restrict methods forprimitive functions to signatures in which at least one of the classesin the signature is a formal S4 class.Otherwise the internal C code will not look for methods.This is a desirable restriction in principle, since optionalpackages should not be allowed to change the behavior of basic Rcomputations on existing data types.
To see the generic version of a primitive function, usegetGeneric(name). The functionisGeneric will tell you whether methods are definedfor the function in the current session.
Note that S4 methods can only be set on those primitives which are‘internal generic’, plus%*%.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (Section 10.5 for some details.)
Methods_Details and the links there for a general discussion,dotsMethods for methods that dispatch on..., andsetMethod for method definitions.
## Specify that this package will define methods for plot()setGeneric("plot")## create a new generic function, with a default methodsetGeneric("props", function(object) attributes(object))### A non-standard generic function. It insists that the methods### return a non-empty character vector (a stronger requirement than### valueClass = "character" in the call to setGeneric)setGeneric("authorNames", function(text) { value <- standardGeneric("authorNames") if(!(is(value, "character") && any(nchar(value)>0))) stop("authorNames methods must return non-empty strings") value })## the asRObject generic function, from package XR## Its default method just returns object## See the reference, Chapter 12 for methodssetGeneric("asRObject", function(object, evaluator) { object})## Specify that this package will define methods for plot()setGeneric("plot")## create a new generic function, with a default methodsetGeneric("props",function(object) attributes(object))### A non-standard generic function. It insists that the methods### return a non-empty character vector (a stronger requirement than### valueClass = "character" in the call to setGeneric)setGeneric("authorNames",function(text){ value<- standardGeneric("authorNames")if(!(is(value,"character")&& any(nchar(value)>0))) stop("authorNames methods must return non-empty strings") value})## the asRObject generic function, from package XR## Its default method just returns object## See the reference, Chapter 12 for methodssetGeneric("asRObject",function(object, evaluator){ object})
ThesetGroupGeneric function behaves likesetGenericexcept that it constructs a group generic function, differing in twoways from an ordinary generic function. First, this function cannotbe called directly, and the body of the function created will containa stop call with this information. Second, the group generic functioncontains information about the known members of the group, used tokeep the members up to date when the group definition changes, throughchanges in the search list or direct specification of methods, etc.
All members of the group must have the identical argument list.
setGroupGeneric(name, def= , group=list(), valueClass=character(), knownMembers=list(), package= , where= )setGroupGeneric(name, def=, group=list(), valueClass=character(), knownMembers=list(), package=, where=)
name | the character string name of the generic function. |
def | A function object. There isn't likely to be an existingnongeneric of this name, so some function needs to be supplied. Anyknown member or other function with the same argument list will do,because the group generic cannot be called directly. |
group,valueClass | arguments to pass to |
knownMembers | the names of functions that areknown to be members of this group. This information is used toreset cached definitions of the member generics when informationabout the group generic is changed. |
package,where | passed to |
ThesetGroupGeneric function exists for its side effect: saving thegeneric function to allow methods to be specified later. It returnsname.
Chambers, John M. (2016)Extending RChapman & Hall
Methods_Details and the links there for a general discussion,dotsMethods for methods that dispatch on..., andsetMethod for method definitions.
## Not run: ## the definition of the "Logic" group generic in the methods packagesetGroupGeneric("Logic", function(e1, e2) NULL, knownMembers = c("&", "|"))## End(Not run)## Not run:## the definition of the "Logic" group generic in the methods packagesetGroupGeneric("Logic",function(e1, e2)NULL, knownMembers= c("&","|"))## End(Not run)
setIs is an explicit alternativeto thecontains= argument tosetClass. It isonly needed to create relations with explicit test or coercion.These have not proved to be of much practical value, so thisfunction should not likely be needed in applications.
Where the programming goal is to define methods for transforming oneclass of objects to another, it is usually better practice to callsetAs(), which requires the transformations to be done explicitly.
setIs(class1, class2, test=NULL, coerce=NULL, replace=NULL, by = character(), where = topenv(parent.frame()), classDef =, extensionObject = NULL, doComplete = TRUE)setIs(class1, class2, test=NULL, coerce=NULL, replace=NULL, by= character(), where= topenv(parent.frame()), classDef=, extensionObject=NULL, doComplete=TRUE)
class1,class2 | the names of the classes between which |
coerce,replace | functions optionally supplied to coerce the object to |
test | aconditional relationship isdefined by supplying this function. Conditional relations arediscouraged 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 |
Arranging for a class to inherit from another class is a key tool inprogramming. InR, there are three basic techniques, the first twoproviding what is called “simple” inheritance, the preferred form:
By thecontains= argument in a call tosetClass. Thisis and should be the most common mechanism. It arranges that the newclass contains all the structure of the existing class, and inparticular all the slots with the same class specified. Theresulting class extension is defined to besimple, withimportant implications for method definition (see the section onthis topic below).
Makingclass1 a subclass of a virtual classeither by a call tosetClassUnion to make thesubclass a member of a new class union, or by a call tosetIs to add a class to an existing class union or as a newsubclass of an existing virtual class. In either case, theimplication should be that methods defined for the class union orother superclass all work correctly for the subclass. This maydepend on some similarity in the structure of the subclasses orsimply indicate that the superclass methods are defined in termsof generic functions that apply to all the subclasses. Theserelationships are also generally simple.
Supplyingcoerce andreplace arguments tosetAs.R allows arbitrary inheritance relationships, using the samemechanism for defining coerce methods by a call tosetAs. The difference between the two is simplythatsetAs will require a call toasfor a conversion to take place, whereas after the call tosetIs, objects will be automatically converted tothe superclass.
The automatic feature is the dangerous part, mainly because itresults in the subclass potentially inheriting methods that donot work. See the section on inheritance below. If the twoclasses involved do not actually inherit a large collection ofmethods, as in the first example below, the danger may berelatively slight.
If the superclass inherits methods where the subclass has only adefault or remotely inherited method, problems are more likely.In this case, a generalrecommendation is to use thesetAs mechanisminstead, unless there is a strong counter reason. Otherwise, be prepared tooverride some of the methods inherited.
With this caution given, the rest of this section describes whathappens whencoerce= andreplace= arguments are suppliedtosetIs.
Thecoerce andreplace arguments are functions thatdefine how to coerce aclass1 object toclass2, andhow to replace the part of the subclass object that corresponds toclass2. The first of these is a function of one argumentwhich should befrom, and the second of two arguments(from,value). For details, see the section on coercefunctions below .
Whenby is specified, the coerce process first coerces tothis class and then toclass2. It's unlikely youwould use theby argument directly, but it is used in definingcached information about classes.
The value returned (invisibly) bysetIs is the revised class definition ofclass1.
Thecoerce argument is a function that turns aclass1 object into aclass2 object. Thereplace argument is a function of two arguments that modifies aclass1object (the first argument) to replace the part of it thatcorresponds toclass2 (supplied asvalue, the secondargument). It then returns the modified object as the value of thecall. In other words, it acts as a replacement method toimplement the expressionas(object, class2) <- value.
The easiest way to think of thecoerce andreplacefunctions is by thinking of the case thatclass1containsclass2 in the usual sense, by including the slots ofthe second class. (To repeat, in this situation you would not callsetIs, but the analogy shows what happens when you do.)
Thecoerce function in this case would just make aclass2 object by extracting the corresponding slots from theclass1 object. Thereplace function would replace intheclass1 object the slots corresponding toclass2,and return the modified object as its value.
For additional discussion of these functions, seethe documentation of thesetAs function. (Unfortunately, argumentdef to that function corresponds to argumentcoerce here.)
The inheritance relationship can also be conditional, if a function is supplied as thetest argument. This should be a function of one argumentthat returnsTRUE orFALSE according to whether theobject supplied satisfies the relationis(object, class2).Conditional relations betweenclasses are discouraged in general because they require a per-objectcalculation to determine their validity. They cannot be appliedas efficiently as ordinary relations and tend to make the code thatuses them harder to interpret.NOTE: conditional inheritanceis not used to dispatch methods. Methods for conditionalsuperclasses will not be inherited. Instead, a method for thesubclass should be defined that tests the conditional relationship.
A method written for a particular signature (classes matched to oneor more formal arguments to the function) naturally assumes that theobjects corresponding to the arguments can be treated as coming fromthe corresponding classes. The objects will have all the slots andavailable methods for the classes.
The code that selects and dispatches the methods ensures that thisassumption is correct. If the inheritance was “simple”, thatis, defined by one or more uses of thecontains= argument ina call tosetClass, no extra work is generallyneeded. Classes are inherited from the superclass, with the samedefinition.
When inheritance is defined by a general call tosetIs, extra computations are required. This form ofinheritance implies that the subclass doesnot just containthe slots of the superclass, but instead requires the explicit callto the coerce and/or replace method. To ensure correct computation,the inherited method is supplemented by calls toasbefore the body of the method is evaluated.
The calls toas generated in this case have theargumentstrict = FALSE, meaning that extra information canbe left in the converted object, so long as it has all theappropriate slots. (It's this option that allows simple subclassobjects to be used without any change.) When you are writing yourcoerce method, you may want to take advantage of that option.
Methods inherited through non-simple extensions can result in ambiguitiesor unexpected selections. Ifclass2 is a specialized classwith just a few applicable methods, creating the inheritancerelation may have little effect on the behavior ofclass1.But ifclass2 is a class with many methods, you mayfind that you now inherit some undesirable methods forclass1, 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 thatclass. But the S3 class is ambiguous, and in fact is"character" rather than"factor".
For some generic functions, methods inherited by non-simpleextensions are either known to be invalid or sufficiently likely tobe so that the generic function has been defined to exclude suchinheritance. For exampleinitialize methods mustreturn an object of the target class; this is straightforward if theextension is simple, because no change is made to the argumentobject, but is essentially impossible. For this reason, the genericfunction insists on only simple extensions for inheritance. See thesimpleInheritanceOnly argument tosetGenericfor the mechanism. You can use this mechanism when defining newgeneric functions.
If you get into problems with functions that do allow non-simpleinheritance, there are two basic choices. Eitherback off from thesetIs call and settle for explicit coercingdefined by a call tosetAs; or, define explicitmethods involvingclass1 to override the bad inheritedmethods. The first choice is the safer, when there are seriousproblems.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
## 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 (classes taken from examples in ?setClass):## A simple class with two slotssetClass("track", slots = c(x="numeric", y="numeric"))## A class extending the previous, adding one more slotsetClass("trackCurve", contains = "track", slots = c(smooth = "numeric"))## A class similar to "trackCurve", but with different structure## allowing matrices for the "y" and "smooth" slotssetClass("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 matricessetIs("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 coercesetIs("stringsDated", "factor", coerce = function(from) factor([email protected]), replace= function(from, value) {[email protected] <- 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 ambiguitiessetClass("factorDated", contains = "factor", slots = c(stamp="POSIXt"))fd <- new("factorDated", factor(ll), stamp = Sys.time())identical(levels(fd), levels(as(fd, "factor")))## 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 (classes taken from examples in ?setClass):## A simple class with two slotssetClass("track", slots= c(x="numeric", y="numeric"))## A class extending the previous, adding one more slotsetClass("trackCurve", contains="track", slots= c(smooth="numeric"))## A class similar to "trackCurve", but with different structure## allowing matrices for the "y" and "smooth" slotssetClass("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 matricessetIs("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 coercesetIs("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 ambiguitiessetClass("factorDated", contains="factor", slots= c(stamp="POSIXt"))fd<- new("factorDated", factor(ll), stamp= Sys.time())identical(levels(fd), levels(as(fd,"factor")))
These functions provide a mechanism for packages to specifycomputations to be done during the loading of a package namespace.Such actions are a flexible way to provide information only available atload time (such as locations in a dynamically linked library).
A call tosetLoadAction() orsetLoadActions() specifiesone or more functions to be called when the corresponding namespace isloaded, with the ... argument names being used as identifyingnames for the actions.
getLoadActions reports the currently defined load actions,given a package's namespace as its argument.
hasLoadAction returnsTRUE if a load actioncorresponding to the given name has previously been set for thewhere namespace.
evalOnLoad() andevalqOnLoad() schedule a specificexpression for evaluation at load time.
setLoadAction(action, aname=, where=)setLoadActions(..., .where=)getLoadActions(where=)hasLoadAction(aname, where=)evalOnLoad(expr, where=, aname=)evalqOnLoad(expr, where=, aname=)setLoadAction(action, aname=, where=)setLoadActions(..., .where=)getLoadActions(where=)hasLoadAction(aname, where=)evalOnLoad(expr, where=, aname=)evalqOnLoad(expr, where=, aname=)
action,... | functions of one or more arguments, to be called when this package isloaded. The functions will be called with one argument (the packagenamespace) so all following arguments must have default values. If the elements of ... are named, these names will be used for thecorresponding load metadata. |
where,.where | the namespace of the package for which the list of load actions aredefined. This argument is normally omitted if the call comes from thesource code for the package itself, but will be needed if a packagesupplies load actions for another package. |
aname | the name for the action. If an action is set withoutsupplying a name, the default uses the position in the sequence ofactions specified ( |
expr | an expression to be evaluated in a load action inenvironment |
TheevalOnLoad() andevalqOnLoad() functions are forconvenience. They construct a function to evaluate the expression andcallsetLoadAction() to schedule a call to that function.
Each of the functions supplied as an argument tosetLoadAction()orsetLoadActions() is saved as metadata in the namespace,typically that of the package containing the call tosetLoadActions(). When this package's namespace is loaded, eachof these functions will be called. Action functions are called in theorder they are supplied tosetLoadActions(). The objectsassigned have metadata names constructed from the names supplied in thecall; unnamed arguments are taken to be named by their position in thelist of actions (".1", etc.).
Multiple calls tosetLoadAction() orsetLoadActions()can be used in a package's code; the actions will be scheduled after anypreviously specified, except if the name given tosetLoadAction()is that of an existing action. In typical applications,setLoadActions() is more convenient when calling from thepackage's own code to set several actions. Calls tosetLoadAction() are more convenient if the action name is to beconstructed, which is more typical when one package constructs loadactions for another package.
Actions can be revised by assigning with the same name, actual orconstructed, in a subsequent call. The replacement must still be avalid function, but can of course do nothing if the intention was toremove a previously specified action.
The functions must have at least one argument. They will be called withone argument, the namespace of the package. The functions will becalled at the end of processing of S4 metadata, after dynamicallylinking any compiled code, the call to.onLoad(), if any, andcaching method and class definitions, but before the namespace issealed. (Load actions are only called if methods dispatch is on.)
Functions may therefore assign or modify objects in the namespacesupplied as the argument in the call. The mechanism allows packagesto save information not available until load time, such as valuesobtained from a dynamically linked library.
Load actions should be contrasted with user load hooks supplied bysetHook(). User hooks are generally provided fromoutside the package and are run after the namespace has been sealed.Load actions are normally part of the package code, and the list ofactions is normally established when the package is installed.
Load actions can be supplied directly in the source code for apackage. It is also possible and useful to provide facilities in onepackage to create load actions in another package. The software needsto be careful to assign the action functions in the correctenvironment, namely the namespace of the target package.
setLoadAction() andsetLoadActions() are called fortheir side effect and return no useful value.
getLoadActions() returns a named list of the actions in thesupplied namespace.
hasLoadAction() returnsTRUE if the specified actionname appears in the actions for this package.
setHook for safer (since they are run after thenamespace is sealed) and more comprehensive versions in thebase package.
## Not run: ## in the code for some package## ... somewhere elsesetLoadActions(function(ns) cat("Loaded package", sQuote(getNamespaceName(ns)), "at", format(Sys.time()), "\n"), setCount = function(ns) assign("myCount", 1, envir = ns), function(ns) assign("myPointer", getMyExternalPointer(), envir = ns)) ... somewhere laterif(countShouldBe0) setLoadAction(function(ns) assign("myCount", 0, envir = ns), "setCount")## End(Not run)## Not run:## in the code for some package## ... somewhere elsesetLoadActions(function(ns) cat("Loaded package", sQuote(getNamespaceName(ns)),"at", format(Sys.time()),"\n"), setCount=function(ns) assign("myCount",1, envir= ns),function(ns) assign("myPointer", getMyExternalPointer(), envir= ns))... somewhere laterif(countShouldBe0) setLoadAction(function(ns) assign("myCount",0, envir= ns),"setCount")## End(Not run)
Create a method for a generic function, corresponding to a signature of classes for the arguments. Standard usage will be of the form:
setMethod(f, signature, definition)
wheref is the name of the function,signature specifies the argument classes for which the method applies anddefinition is the function definition for the method.
setMethod(f, signature=character(), definition, where = topenv(parent.frame()), valueClass = NULL, sealed = FALSE)setMethod(f, signature=character(), definition, where= topenv(parent.frame()), valueClass=NULL, sealed=FALSE)
f | The character-string name of the generic function. The unquoted name usually works as well (evaluating to the generic function), except for a few functions in the base package. |
signature | The classes required for some of the arguments. Most applications just require one or two character strings matching the first argument(s) in the signature. More complicated cases follow R's rule for argument matching. See the details below; however, if the signature is not trivial, you should use |
definition | A function definition, which will become the methodcalled when the arguments in a call to |
where,valueClass,sealed | These arguments are allowedbut either obsolete or rarely appropriate.
|
The function exists for its side-effect. The definition will be stored in a special metadata object and incorporated in the generic function when the corresponding package is loaded into an R session.
When defining methods, it's important to ensure that methods areselected correctly; in particular, packages should be designed toavoid ambiguous method selection.
To describe method selection, consider first the case where only oneformal argument is in the active signature; that is, there is only oneargument,x say, for which methods have been defined.The generic function has a table of methods, indexed by the class forthe argument in the calls tosetMethod.If there is a method in the table for the class ofx in thecall, this method is selected.
If not, the next best methods would correspond to the directsuperclasses ofclass(x)—those appearing in thecontains= argument when that class was defined.If there is no method for any of these, the next best would correspondto the direct superclasses of the first set of superclasses, and soon.
The first possible source of ambiguity arises if the class has severaldirect superclasses and methods have been defined for more than one ofthose;R will consider these equally valid and report an ambiguous choice.If your package has the class definition forclass(x), then youneed to define a method explicitly for this combination of genericfunction and class.
When more than one formal argument appears in the method signature,Rrequires the “best” method to be chosen unambiguously for eachargument.Ambiguities arise when one method is specific about one argument whileanother is specific about a different argument.A call that satisfies both requirements is then ambiguous: The twomethods look equally valid, which should be chosen?In such cases the package needs to add a third method requiring botharguments to match.
The most common examples arise with binary operators. Methods may bedefined for individual operators, for special groups of operators such asArith or for groupOps.
If a package defines methods for generic functions, those methodsshould be exported if any of the classes involved are exported; inother words, if someone using the package might expect these methodsto be called.Methods are exported by including anexportMethods() directivein theNAMESPACE file for the package, with the arguments tothe directive being the names of the generic functions for whichmethods have been defined.
Exporting methods is always desirable in the sense of declaring whatyou want to happen, in that you do expect users to find such methods.It can be essential in the case that the method was defined for afunction that is not originally a generic function in its own package(for example,plot() in thegraphics package). In thiscase it may be that the version of the function in theR session isnot generic, and your methods will not be called.
Exporting methods for a function also exports the generic version ofthe function.Keep in mind that this doesnot conflict with the function asit was originally defined in another package; on the contrary, it'sdesigned to ensure that the function in theR session dispatchesmethods correctly for your classes and continues to behave as expectedwhen no specific methods apply. SeeMethods_Details for the actual mechanism.
The call tosetMethod stores the supplied method definition inthe metadata table for this generic function in the environment,typically the global environment or the namespace of a package.In the case of a package, the table object becomes part of the namespace or environment of thepackage.When the package is loaded into a later session, themethods will be merged into the table of methods in the correspondinggeneric function object.
Generic functions are referenced by the combination of the function name andthe package name;for example, the function"show" from the package"methods".Metadata for methods is identified by the two strings; in particular, thegeneric function object itself has slots containing its name and itspackage name.The package name of a generic is set according to the packagefrom which it originally comes; in particular, and frequently, thepackage where a non-generic version of the function originated.For example, generic functions for all the functions in packagebase willhave"base" as the package name, although none of them is anS4 generic on that package.These include most of the base functions that are primitives, rather thantrue functions; see the section on primitive functions in thedocumentation forsetGeneric for details.
Multiple packages can have methods for the same generic function; thatis, for the same combination of generic function name and packagename.Even though the methods are stored in separate tables in separateenvironments, loading the corresponding packages adds the methods tothe table in the generic function itself, for the duration of the session.
The classnames in the signature can be any formal class, including basicclasses such as"numeric","character", and"matrix". Two additional special class names can appear:"ANY", meaning that this argument can have any class at all;and"missing", meaning that this argumentmust notappear in the call in order to match this signature. Don't confusethese two: if an argument isn't mentioned in a signature, itcorresponds implicitly to class"ANY", not to"missing". See the example below. Old-style (‘S3’)classes can also be used, if you need compatibility with these, butyou should definitely declare these classes by callingsetOldClass if you want S3-style inheritance to work.
Method definitions canhave default expressions for arguments, but only ifthe generic function must havesome default expression for thesame argument. (This restriction is imposed by the wayR managesformal arguments.)If so, and if the corresponding argument ismissing in the call to the generic function, the default expressionin the method is used. If the method definition has no default forthe argument, then the expression supplied in the definition of thegeneric function itself is used, but note that this expression willbe evaluated using the enclosing environment of the method, not ofthe generic function.Method selection doesnot evaluate default expressions.All actual (non-missing) arguments in the signature of thegeneric function will be evaluated when a method is selected—whenthe call tostandardGeneric(f) occurs.Note that specifying class"missing" in the signaturedoes not require any default expressions.
It is possible to have some differences between theformal arguments to a method supplied tosetMethod and thoseof the generic. Roughly, if the generic has ... as one of itsarguments, then the method may have extra formal arguments, whichwill be matched from the arguments matching ... in the call tof. (What actually happens is that a local function iscreated inside the method, with the modified formal arguments, and the methodis re-defined to call that local function.)
Method dispatch tries to match the class of the actual arguments in acall to the available methods collected forf. If there is amethod defined for the exact same classes as in this call, thatmethod is used. Otherwise, all possible signatures are consideredcorresponding to the actual classes or to superclasses of the actualclasses (including"ANY").The method having the least distance from the actual classes ischosen; if more than one method has minimal distance, one is chosen(the lexicographically first in terms of superclasses) but a warningis issued.All inherited methods chosen are stored in another table, so thatthe inheritance calculations only need to be done once per sessionper sequence of actual classes.SeeMethods_Details and Section 10.7 of the reference for more details.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
Methods_for_Nongenerics discusses method definition forfunctions that are not generic functions in their original package;Methods_for_S3 discusses the integration of formal methods with theolder S3 methods.
method.skeleton, which is the recommended way to generate a skeleton of the call tosetMethod, with the correct formal arguments and other details.
Methods_Details and the links there for a general discussion,dotsMethods for methods that dispatch on“...”, andsetGeneric for generic functions.
## examples for a simple class with two numeric slots.## (Run example(setMethod) to see the class and function definitions)## methods for plotting track objects #### First, with only one object as argument, plot the two slots## y must be included in the signature, it would default to "ANY"setMethod("plot", signature(x="track", y="missing"), function(x, y, ...) plot(x@x, x@y, ...))## plot numeric data on either axis against a track object## (reducing the track object to the cumulative distance along the track)## Using a short form for the signature, which matches like formal argumentssetMethod("plot", c("track", "numeric"), function(x, y, ...) plot(cumdist(x@x, x@y), y, xlab = "Distance",...))## and similarly for the other axissetMethod("plot", c("numeric", "track"), function(x, y, ...) plot(x, cumdist(y@x, y@y), ylab = "Distance",...))t1 <- new("track", x=1:20, y=(1:20)^2)plot(t1)plot(qnorm(ppoints(20)), t1)## Now a class that inherits from "track", with a vector for data at## the points setClass("trackData", contains = c("numeric", "track"))tc1 <- new("trackData", t1, rnorm(20))## a method for plotting the object## This method has an extra argument, allowed because ... is an## argument to the generic function.setMethod("plot", c("trackData", "missing"),function(x, y, maxRadius = max(par("cin")), ...) { plot(x@x, x@y, type = "n", ...) symbols(x@x, x@y, circles = abs(x), inches = maxRadius) })plot(tc1)## Without other methods for "trackData", methods for "track"## will be selected by inheritanceplot(qnorm(ppoints(20)), tc1)## defining methods for primitive function.## Although "[" and "length" are not ordinary functions## methods can be defined for them.setMethod("[", "track", function(x, i, j, ..., drop) { x@x <- x@x[i]; x@y <- x@y[i] x })plot(t1[1:15])setMethod("length", "track", function(x)length(x@y))length(t1)## Methods for binary operators## A method for the group generic "Ops" will apply to all operators## unless a method for a more specific operator has been defined.## For one trackData argument, go on with just the data partsetMethod("Ops", signature(e1 = "trackData"), function(e1, e2) callGeneric([email protected], e2))setMethod("Ops", signature(e2 = "trackData"), function(e1, e2) callGeneric(e1,[email protected]))## At this point, the choice of a method for a call with BOTH## arguments from "trackData" is ambiguous. We must define a method.setMethod("Ops", signature(e1 = "trackData", e2 = "trackData"), function(e1, e2) callGeneric([email protected],[email protected]))## (well, really we should only do this if the "track" part## of the two arguments matched)tc1 +11/tc1all(tc1 == tc1)## examples for a simple class with two numeric slots.## (Run example(setMethod) to see the class and function definitions)## methods for plotting track objects#### First, with only one object as argument, plot the two slots## y must be included in the signature, it would default to "ANY"setMethod("plot", signature(x="track", y="missing"),function(x, y,...) plot(x@x, x@y,...))## plot numeric data on either axis against a track object## (reducing the track object to the cumulative distance along the track)## Using a short form for the signature, which matches like formal argumentssetMethod("plot", c("track","numeric"),function(x, y,...) plot(cumdist(x@x, x@y), y, xlab="Distance",...))## and similarly for the other axissetMethod("plot", c("numeric","track"),function(x, y,...) plot(x, cumdist(y@x, y@y), ylab="Distance",...))t1<- new("track", x=1:20, y=(1:20)^2)plot(t1)plot(qnorm(ppoints(20)), t1)## Now a class that inherits from "track", with a vector for data at## the points setClass("trackData", contains= c("numeric","track"))tc1<- new("trackData", t1, rnorm(20))## a method for plotting the object## This method has an extra argument, allowed because ... is an## argument to the generic function.setMethod("plot", c("trackData","missing"),function(x, y, maxRadius= max(par("cin")),...){ plot(x@x, x@y, type="n",...) symbols(x@x, x@y, circles= abs(x), inches= maxRadius)})plot(tc1)## Without other methods for "trackData", methods for "track"## will be selected by inheritanceplot(qnorm(ppoints(20)), tc1)## defining methods for primitive function.## Although "[" and "length" are not ordinary functions## methods can be defined for them.setMethod("[","track",function(x, i, j,..., drop){ x@x<- x@x[i]; x@y<- x@y[i] x})plot(t1[1:15])setMethod("length","track",function(x)length(x@y))length(t1)## Methods for binary operators## A method for the group generic "Ops" will apply to all operators## unless a method for a more specific operator has been defined.## For one trackData argument, go on with just the data partsetMethod("Ops", signature(e1="trackData"),function(e1, e2) callGeneric(e1@.Data, e2))setMethod("Ops", signature(e2="trackData"),function(e1, e2) callGeneric(e1, e2@.Data))## At this point, the choice of a method for a call with BOTH## arguments from "trackData" is ambiguous. We must define a method.setMethod("Ops", signature(e1="trackData", e2="trackData"),function(e1, e2) callGeneric(e1@.Data, e2@.Data))## (well, really we should only do this if the "track" part## of the two arguments matched)tc1+11/tc1all(tc1== tc1)
Register an old-style (a.k.a. ‘S3’) class as a formally definedclass. Simple usage will be of the form:
setOldClass(Classes)
whereClasses is the character vector that would be theclass attribute of the S3 object. Calls tosetOldClass() in the code for a packageallow the class to be used as a slot in formal (S4) classes and insignatures for methods (seeMethods_for_S3).Formal classes can also contain a registered S3 class (seeS3Part for details).
If the S3 class has a known set of attributes, anequivalent S4 class can be specified byS4Class= in the call tosetOldClass(); see the section “Known Attributes”.
setOldClass(Classes, prototype, where, test = FALSE, S4Class)setOldClass(Classes, prototype, where, test=FALSE, S4Class)
Classes | A character vector, giving the names for S3classes, as they would appear on the right side of an assignment ofthe In addition to S3 classes, an object type or other valid data partcan be specified, if the S3 class is known to require its data tobe of that form. |
S4Class | optionally, the class definition or the class nameof an S4 class. The new class will have all the slots and otherproperties of this class, plus any S3 inheritance implied bymultiple names in the |
prototype,where,test | These arguments are currentlyallowed, but not recommended in typical applications.
|
The name (or each of the names) inClasses will be defined as an S4 class, extending classoldClass,which is the ‘root’ of all old-style classes. S3 classeswith multiple names in their class attribute will have acorresponding inheritance as formal classes. See the"mlm" example.
S3 classes haveno formal definition, and therefore no formally defined slots.If no S4 class is supplied as a model, the class created will be avirtual class.If a virtual class (any virtual class) is used for a slot in another class, then theinitializing method for the class needs to put something legal inthat slot; otherwise it will be set toNULL.
SeeMethods_for_S3 for the details of method dispatch andinheritance with mixed S3 and S4 methods.
Some S3 classes cannot be represented as an ordinary combination of S4classes and superclasses, because objects with the same initialstring in the class attribute can have different strings following.Such classes are fortunately rare. They violate the basic idea ofobject-oriented programming and should be avoided. If you must deal with them, it is still possible to registersuch classes as S4 classes, but now the inheritance has to be verifiedfor each object, and you must callsetOldClass with argumenttest=TRUE.
Many of the widely used S3 classes in the standard R distributioncome pre-defined for use with S4. These don't need to be explicitlydeclared in your package (although it does no harm to do so).
The list.OldClassesList contains the old-style classes thatare defined by the methods package. Each element of the list is acharacter vector, with multiple strings if inheritance is included.Each element of the list was passed tosetOldClass whencreating themethods package; therefore, these classes can be usedinsetMethod calls, with the inheritance as implied bythe list.
A further specification of an S3 class can be madeif theclass is guaranteed to have some attributes of known class (where aswith slots, “known” means that the attribute is an object ofa specified class, or a subclass of that class).
In this case, the call tosetOldClass() can supply an S4 classdefinition representing the known structure. Since S4 slots areimplemented as attributes (largely for just this reason), the knownattributes can be specified in the representation of the S4 class.The usual technique will be to create an S4 class with the desiredstructure, and then supply the class name or definition as theargumentS4Class= tosetOldClass().
See the definition of class"ts" in the examples below andthedata.frame example in Section 10.2 of the reference.The call tosetClass to create the S4 class can use the sameclass name, as here, so long as the call tosetOldClassfollows in the same package. For clarity it should be the nextexpression in the same file.
In the example, we define"ts" as a vector structure with anumeric slot for"tsp". The validity of this definition relieson an assertion that all the S3 code for this class is consistent withthat definition; specifically, that all"ts" objects willbehave as vector structures and will have a numeric"tsp"attribute. We believe this to be true of all the base code inR, butas always with S3 classes, no guarantee is possible.
The S4 class definition can have virtual superclasses (as inthe"ts" case) if the S3 class is asserted to behaveconsistently with these (in the example, time-series objects areasserted to be consistent with thestructure class).
Failures of the S3 class to live up to its assertedbehavior will usually go uncorrected, since S3 classes inherentlyhave no definition, and the resulting invalid S4 objects can causeall sorts of grief. Many S3 classes are not candidates for knownslots, either because the presence or class of the attributes arenot guaranteed (e.g.,dimnames in arrays, although these arenot even S3 classes), or because the class uses named components ofa list rather than attributes (e.g.,"lm"). An attributethat is sometimes missing cannot be represented as a slot, not evenby pretending that it is present with class"NULL", becauseattributes, unlike slots, can not have valueNULL.
One irregularity that is usually tolerated, however, is to optionallyadd other attributes to those guaranteed to exist (for example,"terms" in"data.frame" objects returned bymodel.frame). Validity checks byvalidObject ignore extra attributes; even if this checkis tightened in the future, classes extending S3 classes would likelybe exempted because extra attributes are so common.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10, particularly Section 10.8)
require(stats)## "lm" and "mlm" are predefined; if they were not this would do it:## Not run: setOldClass(c("mlm", "lm"))## End(Not run)## Define a new generic function to compute the residual degrees of freedomsetGeneric("dfResidual", function(model) stop(gettextf( "This function only works for fitted model objects, not class %s", class(model))))setMethod("dfResidual", "lm", function(model)model$df.residual)## dfResidual will work on mlm objects as well as lm objectsmyData <- data.frame(time = 1:10, y = (1:10)^.5)myLm <- lm(cbind(y, y^3) ~ time, myData)## two examples extending S3 class "lm": class "xlm" directly## and "ylm" indirectlysetClass("xlm", slots = c(eps = "numeric"), contains = "lm")setClass("ylm", slots = c(header = "character"), contains = "xlm")ym1 = new("ylm", myLm, header = "Example", eps = 0.)## for more examples, see ?\link{S3Class}.## Not run: ## The code in R that defines "ts" as an S4 classsetClass("ts", contains = "structure", slots = c(tsp = "numeric"), prototype(NA, tsp = rep(1,3))) # prototype to be a legal S3 time-series## and now registers it as an S3 classsetOldClass("ts", S4Class = "ts", where = envir)## End(Not run)require(stats)## "lm" and "mlm" are predefined; if they were not this would do it:## Not run:setOldClass(c("mlm","lm"))## End(Not run)## Define a new generic function to compute the residual degrees of freedomsetGeneric("dfResidual",function(model) stop(gettextf("This function only works for fitted model objects, not class %s", class(model))))setMethod("dfResidual","lm",function(model)model$df.residual)## dfResidual will work on mlm objects as well as lm objectsmyData<- data.frame(time=1:10, y=(1:10)^.5)myLm<- lm(cbind(y, y^3)~ time, myData)## two examples extending S3 class "lm": class "xlm" directly## and "ylm" indirectlysetClass("xlm", slots= c(eps="numeric"), contains="lm")setClass("ylm", slots= c(header="character"), contains="xlm")ym1= new("ylm", myLm, header="Example", eps=0.)## for more examples, see ?\link{S3Class}.## Not run:## The code in R that defines "ts" as an S4 classsetClass("ts", contains="structure", slots= c(tsp="numeric"), prototype(NA, tsp= rep(1,3)))# prototype to be a legal S3 time-series## and now registers it as an S3 classsetOldClass("ts", S4Class="ts", where= envir)## End(Not run)
Display the object, by printing, plotting or whatever suits itsclass. This function exists to be specialized by methods. Thedefault method callsshowDefault.
Formal methods forshow willusually be invoked for automatic printing (see the details).
show(object)show(object)
object | Any R object |
Objects from an S4 class (a class defined by a call tosetClass) will be displayed automatically is if by acall toshow. S4 objects that occur as attributes of S3objects will also be displayed in this form; conversely, S3 objectsencountered as slots in S4 objects will be printed using the S3convention, as if by a call toprint.
Methods defined forshow will only be inherited by simpleinheritance, since otherwise the method would not receive thecomplete, original object, with misleading results. See thesimpleInheritanceOnly argument tosetGeneric andthe discussion insetIs for the general concept.
show returns an invisibleNULL.
showMethods prints all the methods for one or morefunctions.
## following the example shown in the setMethod documentation ...setClass("track", slots = c(x="numeric", y="numeric"))setClass("trackCurve", contains = "track", slots = c(smooth = "numeric"))t1 <- new("track", x=1:20, y=(1:20)^2)tc1 <- new("trackCurve", t1)setMethod("show", "track", function(object)print(rbind(x = object@x, y=object@y)))## The method will now be used for automatic printing of t1t1## Not run: [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]x 1 2 3 4 5 6 7 8 9 10 11 12y 1 4 9 16 25 36 49 64 81 100 121 144 [,13] [,14] [,15] [,16] [,17] [,18] [,19] [,20]x 13 14 15 16 17 18 19 20y 169 196 225 256 289 324 361 400## End(Not run)## and also for tc1, an object of a class that extends "track"tc1## Not run: [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]x 1 2 3 4 5 6 7 8 9 10 11 12y 1 4 9 16 25 36 49 64 81 100 121 144 [,13] [,14] [,15] [,16] [,17] [,18] [,19] [,20]x 13 14 15 16 17 18 19 20y 169 196 225 256 289 324 361 400## End(Not run)## following the example shown in the setMethod documentation ...setClass("track", slots= c(x="numeric", y="numeric"))setClass("trackCurve", contains="track", slots= c(smooth="numeric"))t1<- new("track", x=1:20, y=(1:20)^2)tc1<- new("trackCurve", t1)setMethod("show","track",function(object)print(rbind(x= object@x, y=object@y)))## The method will now be used for automatic printing of t1t1## Not run: [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]x123456789101112y149162536496481100121144[,13][,14][,15][,16][,17][,18][,19][,20]x1314151617181920y169196225256289324361400## End(Not run)## and also for tc1, an object of a class that extends "track"tc1## Not run: [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]x123456789101112y149162536496481100121144[,13][,14][,15][,16][,17][,18][,19][,20]x1314151617181920y169196225256289324361400## End(Not run)
Show a summary of the methods for one or more generic functions,possibly restricted to those involving specified classes.
showMethods(f = character(), where = topenv(parent.frame()), classes = NULL, includeDefs = FALSE, inherited = !includeDefs, showEmpty, printTo = stdout(), fdef).S4methods(generic.function, class)showMethods(f= character(), where= topenv(parent.frame()), classes=NULL, includeDefs=FALSE, inherited=!includeDefs, showEmpty, printTo= stdout(), fdef).S4methods(generic.function, class)
f | one or more function names. If omitted, all functionswill be shown that match the other arguments. The argument can also be an expression that evaluates to a singlegeneric function, in whichcase argument |
where | Where to find the generic function, if not supplied as anargument. When |
classes | If argument |
includeDefs | If |
inherited | logical indicating if methods that have been found byinheritance, so far in the session, will be included and marked asinherited. Note that an inherited method will not usually appearuntil it has been used in this session. See |
showEmpty | logical indicating whether methods with no definedmethods matching the other criteria should be shown at all. Bydefault, |
printTo | The connection on which the information will beshown; by default, on standard output. |
fdef | Optionally, the generic function definition to use; ifmissing, one is found, looking in |
generic.function,class | See |
Seemethods for a description of.S4methods.
The name and package of the generic are followed by the list ofsignatures for which methods are currently defined, according to thecriteria determined by the various arguments. Note that the packagerefers to the source of the generic function. Individual methodsfor that generic can come from other packages as well.
When more than one generic function is involved, either as specified orbecausef was missing, the functions are found andshowMethods is recalled for each, including the generic as theargumentfdef. In complicated situations, this can avoid someanomalous results.
IfprintTo isFALSE, the character vector that wouldhave been printed is returned; otherwise the value is the connectionor filename, viainvisible.
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (For the R version.)
Chambers, John M. (1998)Programming with DataSpringer (For the original S4 version.)
setMethod, andGenericFunctionsfor other tools involving methods;selectMethod will show you the method dispatched for aparticular function and signature of classes for the arguments.
methods provides method discovery tools for light-weightinteractive use.
require(graphics)## Assuming the methods for plot## are set up as in the example of help(setMethod),## print (without definitions) the methods that involve class "track":showMethods("plot", classes = "track")## Not run: # Function "plot":# x = ANY, y = track# x = track, y = missing# x = track, y = ANYrequire("Matrix")showMethods("%*%")# many! methods(class = "Matrix")# nothingshowMethods(class = "Matrix")# everythingshowMethods(Matrix:::isDiagonal) # a non-exported generic## End(Not run)if(no4 <- is.na(match("stats4", loadedNamespaces()))) loadNamespace("stats4")showMethods(classes = "mle") # -> a method for show()if(no4) unloadNamespace("stats4")require(graphics)## Assuming the methods for plot## are set up as in the example of help(setMethod),## print (without definitions) the methods that involve class "track":showMethods("plot", classes="track")## Not run:# Function "plot":# x = ANY, y = track# x = track, y = missing# x = track, y = ANYrequire("Matrix")showMethods("%*%")# many! methods(class="Matrix")# nothingshowMethods(class="Matrix")# everythingshowMethods(Matrix:::isDiagonal)# a non-exported generic## End(Not run)if(no4<- is.na(match("stats4", loadedNamespaces()))) loadNamespace("stats4")showMethods(classes="mle")# -> a method for show()if(no4) unloadNamespace("stats4")
"signature" For Method DefinitionsThis class represents the mapping of some of the formalarguments of a function onto the corresponding classes. It is used fortwo slots in theMethodDefinition class.
Objects can be created by calls of the formnew("signature", functionDef, ...). ThefunctionDef argument, if it issupplied as a function object, defines the formal names. The otherarguments define the classes. More typically, the objects arecreated as side effects of defining methods. Either way, note thatthe classes are expected to be well defined, usually because thecorresponding class definitions exist. See the comment on thepackage slot.
.Data:Object of class"character" the class names.
names:Object of class"character" thecorresponding argument names.
package:Object of class"character" thenames of the packages corresponding to the class names. Thecombination of class name and package uniquely defines theclass. In principle, the same class name could appear in morethan one package, in which case thepackage informationis required for the signature to be well defined.
Class"character", from data part.Class"vector", by class "character".
signature(object = "signature"): see thediscussion of objects from the class, above.
classMethodDefinition for the use of this class.
These functions return or set information about the individual slotsin an object.
object@nameobject@name <- valueslot(object, name)slot(object, name, check = TRUE) <- value.hasSlot(object, name)slotNames(x).slotNames(x)getSlots(x)object@nameobject@name<- valueslot(object, name)slot(object, name, check=TRUE)<- value.hasSlot(object, name)slotNames(x).slotNames(x)getSlots(x)
object | An object from a formally defined class. |
name | The name of the slot. The operatortakes a fixed name, which can be unquoted if it is syntactically aname in the language. A slot name can be any non-empty string, butif the name is not made up of letters, numbers, and In the case of the |
value | A new value for the named slot. The value must bevalid for this slot in this object's class. |
check | In the replacement version of |
x | either the name of a class (as character string), or a classdefinition. If given an argument that is neither a character stringnor a class definition, |
The definition of the class specifies all slots directly andindirectly defined for that class. Each slot has a name and anassociated class. Extracting a slot returns an object from thatclass. Setting a slot first coerces the value to the specified slotand then stores it.
Unlike general attributes, slots are not partially matched, and askingfor (or trying to set) a slot with an invalid name for that classgenerates an error.
The@ extraction operator andslotfunction themselves do no checking against the class definition,simply matching the name in the object itself.The replacement forms do check (except forslot in the casecheck=FALSE). So long as slots are set without cheating, theextracted slots will be valid.
Be aware that there are two ways to cheat, both to be avoided butwith no guarantees. The obvious way is to assign a slot withcheck=FALSE. Also, slots inR are implemented asattributes, for the sake of some back compatibility. The currentimplementation does not prevent attributes being assigned, viaattr<-, and such assignments are not checked forlegitimate slot names.
Note that the"@" operators for extraction and replacement areprimitive and actually reside in thebase package.
The replacement versions of"@" andslot() differ inthe computations done to coerce the right side of the assignment tothe declared class of the slot. Both verify that the value providedis from a subclass of the declared slot class. Theslot()version will go on to call the coerce method if there is one, ineffect doing the computationas(value, slotClass, strict = FALSE). The"@" version just verifies the relation,leaving any coerce to be done later (e.g., when a relevant method isdispatched).
In most uses the result is equivalent, and the"@" versionsaves an extra function call, but if empirical evidence shows that aconversion is needed, either callas() before the replacementor use the replacement version ofslot().
The"@" operator and theslot function extract orreplace the formally defined slots for the object.
FunctionsslotNames andgetSlots return respectively thenames of the slots and the classes associated with the slots in thespecified class definition. Except for its extended interpretation ofx (above),slotNames(x) is justnames(getSlots(x)).
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (For the R version.)
Chambers, John M. (1998)Programming with DataSpringer (For the original S4 version.)
@,Classes_Details,Methods_Details,getClass,names.
setClass("track", slots = c(x="numeric", y="numeric"))myTrack <- new("track", x = -4:4, y = exp(-4:4))slot(myTrack, "x")slot(myTrack, "y") <- log(slot(myTrack, "y"))utils::str(myTrack)getSlots("track") # orgetSlots(getClass("track"))slotNames(class(myTrack)) # is the same asslotNames(myTrack)## Transform such an S4 object to a list, e.g. to "export" it:S4toList <- function(obj) { sn <- slotNames(obj) structure(lapply(sn, slot, object = obj), names = sn)}S4toList(myTrack)setClass("track", slots= c(x="numeric", y="numeric"))myTrack<- new("track", x=-4:4, y= exp(-4:4))slot(myTrack,"x")slot(myTrack,"y")<- log(slot(myTrack,"y"))utils::str(myTrack)getSlots("track")# orgetSlots(getClass("track"))slotNames(class(myTrack))# is the same asslotNames(myTrack)## Transform such an S4 object to a list, e.g. to "export" it:S4toList<-function(obj){ sn<- slotNames(obj) structure(lapply(sn, slot, object= obj), names= sn)}S4toList(myTrack)
The virtual classstructure and classes thatextend it are formal classes analogous to S language structures suchas arrays and time-series.
## The following class names can appear in method signatures,## as the class in as() and is() expressions, and, except for## the classes commented as VIRTUAL, in calls to new()"matrix""array""ts""structure" ## VIRTUAL## The following class names can appear in method signatures,## as the class in as() and is() expressions, and, except for## the classes commented as VIRTUAL, in calls to new()"matrix""array""ts""structure"## VIRTUAL
Objects can be created by calls of the formnew(Class, ...),whereClass is the quoted name of the specific class (e.g.,"matrix"), and the other arguments, if any, are interpreted asarguments to the corresponding function, e.g., to functionmatrix(). There is no particular advantage over calling thosefunctions directly, unless you are writing software designed to workfor multiple classes, perhaps with the class name and the argumentspassed in.
Objects created from the classes"matrix" and"array"are unusual, to put it mildly, and have been for some time. Althoughthey may appear to be objects from these classes, they do not have theinternal structure of either an S3 or S4 class object. In particular,they have no"class" attribute and are not recognized asobjects with classes (that is, bothis.object andisS4 will returnFALSE for such objects).However, methods (both S4 and S3) can be defined for thesepseudo-classes and new classes (both S4 and S3) can inherit from them.
That the objects still behave as if they came from the correspondingclass (most of the time, anyway) results from special coderecognizing such objects being built into the base code ofR.For most purposes, treating the classes in the usual way will work,fortunately. One consequence of the special treatment is that thesetwo classesmay be used as the data part of an S4 class; forexample, you can get away withcontains = "matrix" in a calltosetGeneric to create an S4 class that is a subclassof"matrix". There is no guarantee that everything will workperfectly, but a number of classes have been written in this formsuccessfully.
Note that a class containing"matrix" or"array" willhave a.Data slot with that class. This is the only use of.Data other than as a pseudo-class indicating the type of theobject. In this case the type of the object will be the type of thecontained matrix or array. SeeClasses_Details for a generaldiscussion.
The class"ts" is basically an S3 classthat has been registered with S4, using thesetOldClass mechanism. Versions ofR through 2.7.0treated this class as a pure S4 class, which was in principal a goodidea, but in practice did not allow subclasses to be defined and hadother intrinsic problems. (For example, setting the"tsp" parameters as a slot often fails because the built-inimplementation does not allow the slot to be temporarilyinconsistent with the length of the data. Also, the S4 classprevented the correct specification of the S3 inheritance for class"mts".)
Time-series objects, in contrast to matrices and arrays, have a validS3 class,"ts", registered using an S4-style definition (see thedocumentation forsetOldClass in the examples sectionfor an abbreviated listing of how this is done). The S3inheritance of"mts" in packagestats is alsoregistered.These classes, as well as"matrix" and"array" shouldbe valid in most examples as superclasses for new S4 classdefinitions.
All of these classes have special S4 methods forinitialize that accept the same arguments as the basicgenerator functions,matrix,array, andts, in so far as possible.The limitation is that a class that has more than one non-virtualsuperclass must accept objects from that superclass in the call tonew; therefore, a such a class (what is called a“mixin” in some languages) uses the default method forinitialize, with no special arguments.
The specific classes all extend class"structure", directly, andclass"vector", by class"structure".
Methods are defined to coerce arbitrary objects tothese classes, by calling the corresponding basic function, forexample,as(x, "matrix") callsas.matrix(x).Ifstrict = TRUE in the call toas(), the methodgoes on to delete all other slots and attributes other than thedim anddimnames.
Group methods (see, e.g.,S4groupGeneric)are defined for combinations of structures and vectors (includingspecial cases for array and matrix), implementing the concept ofvector structures as in the reference. Essentially, structurescombined with vectors retain the structure as long as theresulting object has the same length. Structures combined withother structures remove the structure, since there is noautomatic way to determine what should happen to the slotsdefining the structure.
Note that these methods will be activated when a package is loadedcontaining a class that inherits from any of the structureclasses or class"vector".
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (For the R version.)
Chambers, John M. (1998)Programming with DataSpringer (For the original S4 version.)
Becker, R. A., Chambers, J. M. and Wilks, A. R. (1988)The New S Language.Wadsworth & Brooks/Cole (for the original vector structures).
ClassnonStructure, which enforces thealternative model, in which all slots are dropped if any mathtransformation or operation is applied to an object from a classextending one of the basic classes.
showClass("structure")## explore a bit :showClass("ts")(ts0 <- new("ts"))str(ts0)showMethods("Ops") # six methods from these classes, but maybe many moreshowClass("structure")## explore a bit :showClass("ts")(ts0<- new("ts"))str(ts0)showMethods("Ops")# six methods from these classes, but maybe many more
A set of distinct inherited signatures is generated to testinheritance for all the methods of a specified generic function. Ifmethod selection is ambiguous for some of these, a summary of theambiguities is attached to the returned object. This test should beperformed by package authorsbefore releasing a package.
testInheritedMethods(f, signatures, test = TRUE, virtual = FALSE, groupMethods = TRUE, where = .GlobalEnv)testInheritedMethods(f, signatures, test=TRUE, virtual=FALSE, groupMethods=TRUE, where= .GlobalEnv)
f | a generic function or the character string name of one. By default,all currently defined subclasses of all the method signatures for thisgeneric will be examined. The other arguments are mainly options tomodify which inheritance patterns will be examined. |
signatures | An optional set of subclass signatures to use instead of the relevantsubclasses computed by |
test | optional flag to control whether method selection is actually tested.If |
virtual | should virtual classes be included in the relevant subclasses.Normally not, since only the classes of actual arguments will triggerthe inheritance calculation in a call to the generic function.Including virtual classes may be useful if the class has no currentnon-virtual subclasses but you anticipate your users may define suchclasses in the future. |
groupMethods | should methods for the group generic function be included? |
where | the environment in which to look for class definitions. Nearlyalways, use the default global environment after attaching all thepackages with relevant methods and/or class definitions. |
The following description applies when the optional arguments areomitted, the usual case.First, the defining signatures for all methods are computed by callstofindMethodSignatures.From these all the known non-virtual subclasses are found for eachclass that appears in the signature of some method.These subclasses are split into groups according to which class theyinherit from, and only one subclass from each group is retained (foreach argument in the generic signature).So if a method was defined with class"vector" for someargument, one actual vector class is chosen arbitrarily.The case of"ANY" is dealt with specially, since all classesextend it. A dummy, nonvirtual class,".Other", is used tocorrespond to all classes that have no superclasses among those beingtested.
All combinations of retained subclasses for thearguments in the generic signature are then computed.Each row of the resulting matrix is a signature to be tested by a calltoselectMethod.To collect information on ambiguous selections,testInheritedMethods establishes a calling handler for thespecial signal"ambiguousMethodSelection", by setting thecorresponding option.
An object of class"methodSelectionReport". The details ofthis class are currently subject to change. It has slots"target","selected","candidates", and"note", all referring to the ambiguous cases (and so of length0 if there were none). These slots are intended to be examined by theprogrammer to detect and preferably fix ambiguous method selections.The object contains in addition slots"generic", the name ofthe generic function, and"allSelections", giving the vector of labels for allthe signatures tested.
Chambers, John M. (2008)Software for Data Analysis: Programming with RSpringer. (Section 10.6 for basics of method selection.)
Chambers, John M. (2009)Class Inheritance in Rhttps://johnmchambers.su.domains/classInheritance.pdf.
## if no other attached packages have methods for `+` or its group## generic functions, this returns a 16 by 2 matrix of selection## patterns (in R 2.9.0)testInheritedMethods("+")## if no other attached packages have methods for `+` or its group## generic functions, this returns a 16 by 2 matrix of selection## patterns (in R 2.9.0)testInheritedMethods("+")
The classes described here are used by the R functiontrace to create versions of functions and methodsincluding browser calls, etc., and also tountrace thesame objects.
### Objects from the following classes are generated### by calling trace() on an object from the corresponding### class without the "WithTrace" in the name."functionWithTrace""MethodDefinitionWithTrace""MethodWithNextWithTrace""genericFunctionWithTrace""groupGenericFunctionWithTrace"### the following is a virtual class extended by each of the### classes above"traceable"### Objects from the following classes are generated### by calling trace() on an object from the corresponding### class without the "WithTrace" in the name."functionWithTrace""MethodDefinitionWithTrace""MethodWithNextWithTrace""genericFunctionWithTrace""groupGenericFunctionWithTrace"### the following is a virtual class extended by each of the### classes above"traceable"
Objects will be created from these classes by calls totrace.(There is aninitialize method for class"traceable", but you are unlikely to need it directly.)
.Data:The data part, which will be"function"for class"functionWithTrace", and similarly for the otherclasses.
original:Object of the original class; e.g.,"function" for class"functionWithTrace".
Each of the classes extends the corresponding untraced class, from thedata part; e.g.,"functionWithTrace" extends"function".Each of the specific classes extends"traceable", directly,and class"VIRTUAL", by class"traceable".
The point of the specific classes is that objects generated from them,by functiontrace(), remain callable or dispatchable, inaddition to their new trace information.
functiontrace
validObject() tests the validity ofobject related toits class definition; specifically, it checks that all slotsspecified in the class definition are present and that the object inthe slot is from the required class or a subclass of that class.
If the object is valid,TRUE is returned; otherwise, an erroris generated, reporting all the validity failures encountered.If argumenttest isTRUE, the errors are returned as a character vector ratherthan generating an error.
When an object from a class is initialized, the default method forinitialize() callsvalidObject.
A class definition may have a validity method, set by a call tothe functionsetValidity, in the package or environment thatdefines the class (or via thevalidity argument tosetClass). The methodshould be a function of one object that returnsTRUE or a character-stringdescription of the non-validity.If such a method exists, it will be called fromvalidObjectand any strings from failure will be included in the result or theerror message.Any validity methods defined for superclasses (from thecontains=argument tosetClass), will also be called.
validObject(object, test = FALSE, complete = FALSE)setValidity(Class, method, where = topenv(parent.frame()) )getValidity(ClassDef)validObject(object, test=FALSE, complete=FALSE)setValidity(Class, method, where= topenv(parent.frame()))getValidity(ClassDef)
object | any object, but not much will happen unless theobject's class has a formal definition. |
test | logical; if |
complete | logical; if |
Class | the name or class definition of the class whose validitymethod is to be set. |
ClassDef | a class definition object, as from |
method | a validity method; that is, either |
where | an environment to store the modified classdefinition. Should be omitted, specifically for calls from a package that defines the class.The definition will be stored in thenamespace of the package. |
Validity testing takes place ‘bottom up’, checking the slots,then the superclasses, then the object's own validity method, ifthere is one.
For each slot and superclass, the existence of the specified class ischecked.For each slot, the object in the slot is tested for inheritance fromthe corresponding class.Ifcomplete isTRUE,validObject is calledrecursively for the object in the slot.
Then, for each of the classes that this classextends (the ‘superclasses’), the explicit validity method ofthat class is called, if one exists. Finally, the validity method ofobject's class is called, if there is one.
validObject returnsTRUE if the object is valid.Otherwise a vector of strings describing problems found, except thatiftest isFALSE, validity failure generates an error,with the corresponding strings in the error message.
A validity method must be a function of one argument; formally, thatargument should be namedobject.If the argument has a different name,setValidity makes thesubstitution but in obscure cases that might fail, so it's wiser toname theargumentobject.
A good method checks all the possible errors and returns a charactervector citing all the exceptions found, rather than returning afterthe first one.validObject will accumulate these errors in its error messageor its return value.
Note that validity methods do not have to check validity ofsuperclasses:validObject calls such methods explicitly.
Chambers, John M. (2016)Extending R,Chapman & Hall.(Chapters 9 and 10.)
setClass;classclassRepresentation.
setClass("track", slots = c(x="numeric", y = "numeric"))t1 <- new("track", x=1:10, y=sort(stats::rnorm(10)))## A valid "track" object has the same number of x, y valuesvalidTrackObject <- function(object) { if(length(object@x) == length(object@y)) TRUE else paste("Unequal x,y lengths: ", length(object@x), ", ", length(object@y), sep="")}## assign the function as the validity method for the classsetValidity("track", validTrackObject)## t1 should be a valid "track" objectvalidObject(t1)## Now we do something badt2 <- t1t2@x <- 1:20## This should generate an error## Not run: try(validObject(t2))setClass("trackCurve", contains = "track", slots = c(smooth = "numeric"))## all superclass validity methods are used when validObject## is called from initialize() with arguments, so this fails## Not run: trynew("trackCurve", t2)setClass("twoTrack", slots = c(tr1 = "track", tr2 ="track"))## validity tests are not applied recursively by default,## so this object is created (invalidly)tT <- new("twoTrack", tr2 = t2)## A stricter test detects the problem## Not run: try(validObject(tT, complete = TRUE))setClass("track", slots= c(x="numeric", y="numeric"))t1<- new("track", x=1:10, y=sort(stats::rnorm(10)))## A valid "track" object has the same number of x, y valuesvalidTrackObject<-function(object){if(length(object@x)== length(object@y))TRUEelse paste("Unequal x,y lengths: ", length(object@x),", ", length(object@y), sep="")}## assign the function as the validity method for the classsetValidity("track", validTrackObject)## t1 should be a valid "track" objectvalidObject(t1)## Now we do something badt2<- t1t2@x<-1:20## This should generate an error## Not run: try(validObject(t2))setClass("trackCurve", contains="track", slots= c(smooth="numeric"))## all superclass validity methods are used when validObject## is called from initialize() with arguments, so this fails## Not run: trynew("trackCurve", t2)setClass("twoTrack", slots= c(tr1="track", tr2="track"))## validity tests are not applied recursively by default,## so this object is created (invalidly)tT<- new("twoTrack", tr2= t2)## A stricter test detects the problem## Not run: try(validObject(tT, complete = TRUE))