Reputation: 5123
Suppose that there are two packages.
Package_A has this class:
setClass("Person",
slots = c(
name = "character",
age = "numeric"
)
)
setGeneric("age", function(x) standardGeneric("age"))
setMethod("age", "Person", function(x) x@age)
Package_B has a similar class:
setClass("Person",
slots = c(
name = "character",
age = "numeric"
)
)
setGeneric("age", function(x) standardGeneric("age"))
setMethod("age", "Person", function(x) x@age * 10) # notice the difference here
So user has loaded both packages in their working environment:
library(Package_A)
library(Package_B)
In this user's working env, how does R resolve the confusion of creating a "Person" object:
john <- new("Person", name = "John Smith", age = 7)
In this user's working env, how does R resolve calling the right method:
age(john)
Upvotes: 3
Views: 230
Reputation: 20107
getClass()
new()
accepts a classRepresentation
object, which you can retrieve using getClass
. For example, new(getClass('className', where='packageName'))
. However, note that this won't work if you haven't imported the package yet, and also you have defined a new class with the same name. I demonstrate this issue here:
install.packages('lookupTable')
setClass('lookupTable', slots = c(notworking='numeric'))
new(getClass('lookupTable', where='lookupTable'))
#> An object of class "lookupTable"
#> Slot "notworking":
#> numeric(0)
(it has printed the "notworking" slot, which means it is instantiating my custom class, not the correct package version)
new(classNameWithAttribute)
There's an odd but documented feature of new
, which allows you to set the package
attribute on the class name, which actually works perfectly (ie doesn't have the issue mentioned above), if a bit verbosely:
name = 'className'
attr(name, 'package') = 'packageName'
new(name)
There's no reason you couldn't make this into a re-usable function though:
new = function(cls, pkg, ...){
attr(cls, 'package') = pkg
methods::new(cls, ...)
}
new('className', 'packageName')
Of course, all of this can be avoided if the packagers of the S4 classes provide one of two mechanisms:
setClass()
valuesetClass()
has both a side effect (storing the class in the registry), and a return value (a class generator function). So if the packager chooses to store and export the return value in their NAMESPACE
, we can access it later:
# In package "myPackage"
myClass = setClass('myClass')
# In package's NAMESPACE
export(myClass)
# In user's script
new(myPackage::myClass)
For example, you can test this out with the same test package from before:
install.packages('lookupTable')
new(lookupTable::lookupTable)
This is the standard practise in bioconductor. They define a constructor function that has the same name as the class itself. You can then access the constructor function using ::
, and call it instead of new
:
install.packages("BiocManager")
BiocManager::install("IRanges")
new("IRanges")
# Equivalent to
IRanges::IRanges()
Upvotes: 2