Make S4 object act as an S3 class? - r

I would like to write an S4 object such that it can be passed to methods that only take an S3 object. (It seems like setOldClass() might be related to this but it's not clear to me from the documentation?)
e.g. for a minimal example imagine I have the S3 class and function:
myS3 <- list(a = 1, b = 2)
class(myS3) <- "myS3class"
myS3function.myS3class <- function(x) x$a + x$b
and I have the S4 object
setClass("myS4class", representation(a = "numeric", b = "numeric"))
obj <- new("myS4class", a = 1, b = 2)
Is there anything I can do such that
myS3function.myS3class(obj)
gives me same thing as
myS3function.myS3class(myS3)
by modifying only the S4 class?
Edit My rationale for this approach is to take advantage of all existing methods for the S3 class (that may generally come from other packages, etc) without having to rewrite them. I realize one approach is simply to write a coercion method (setAs()) that would turn my S4 object into and S3 object, but then a user would always have to perform this step manually. (While it works, I'm also a bit unclear whether it is bad practice to use setAs() to take an S4 class to an S3 class, rather than mapping between S4 classes).
From the way I read the documentation of setOldClass, it sounds like this can make S3 objects act like S4 objects? Is that correct? If so, my question then is if it possible to do the reverse (maybe by setting the prototype in the S4 class?).
If this is not possible, how about an explanation of the rationale why this would be a bad idea?

Add a method to your S4 class to convert it to being an S3 class.
setGeneric(
"as.myS3class",
function(object)
{
standardGeneric("as.myS3class")
}
)
setMethod(
"as.myS3class",
signature(object = "myS4class"),
function(object)
{
structure(list(a = object#a, b = object#b), class = "myS3class")
}
)
Then you can call the S3 method like this:
myS3function(as.myS3class(obj))

A successful solution is indeed buried in the documentation of setOldClass:
## Examples of S3 classes with guaranteed attributes
## an S3 class "stamped" with a vector and a "date" attribute
## Here is a generator function and an S3 print method.
## NOTE: it's essential that the generator checks the attribute classes
stamped <- function(x, date = Sys.time()) {
if(!inherits(date, "POSIXt"))
stop("bad date argument")
if(!is.vector(x))
stop("x must be a vector")
attr(x, "date") <- date
class(x) <- "stamped"
x
}
print.stamped <- function(x, ...) {
print(as.vector(x))
cat("Date: ", format(attr(x,"date")), "\n")
}
## Now, an S4 class with the same structure:
setClass("stamped4", contains = "vector", representation(date = "POSIXt"))
## We can use the S4 class to register "stamped", with its attributes:
setOldClass("stamped", S4Class = "stamped4")
selectMethod("show", "stamped")
## and then remove "stamped4" to clean up
removeClass("stamped4")
someLetters <- stamped(sample(letters, 10),
ISOdatetime(2008, 10, 15, 12, 0, 0))
st <- new("stamped", someLetters)
st
# show() method prints the object's class, then calls the S3 print method.
stopifnot(identical(S3Part(st, TRUE), someLetters))
# creating the S4 object directly from its data part and slots
new("stamped", 1:10, date = ISOdatetime(1976, 5, 5, 15, 10, 0))
Note that the S4 object can use the S3 print method. What surprised me is that this works for other methods that are defined for the S3 class but not the S4 class even without additional calls to selectMethod. I illustrate this with a more detailed example about my use case with ape::phylo object here: http://carlboettiger.info/2013/10/07/nexml-phylo-class-extension.html

If you want to reuse the one function for both S3 and S4 classes, and not change it, you can write your own definition for $:
f <- function(x, name)
slot(x, name)
setMethod("$", signature=c(x="myS4class"), definition=f)
myS3function.myS4class(obj)
# [1] 3
This seems rather dubious to me, though. For starters, you'll probably also need a similar method for [[, since a function could reference a list element either way:
setMethod("[[", signature=c(x="myS4class", i="character"),
definition=function(x, i) slot(x, i))
And you'll need methods for assignment as well:
setMethod("$<-", signature=c(x="myS4class", value="numeric"),
definition=function(x, name, value) `slot<-`(x, name, check=TRUE, value))
setMethod("[[<-", signature=c(x="myS4class", i="character", value="numeric"),
definition=function(x, i, value) `slot<-`(x, i, check=TRUE, value))
But then you have the problem of referencing by number:
obj[[1]]
# Error in obj[[1]] : this S4 class is not subsettable
So you need yet another method:
g <- function(x, i)
{
slots <- names(getClass("myS4class")#slots)
slot(x, slots[i])
}
setMethod("[[", signature=c(x="myS4class", i="numeric"), g)
All up, it seems like a lot of work for not much gain.

Related

Get internal R functions to use my S4 method

I've created a custom S4 class, and the idea is that it represents a vector that's always sorted, so I don't want sort() to actually do anything to it. So I defined a stub version of sort() for my class:
MyClass <- methods::setClass("MyClass", slots=list(x="numeric"))
setMethod("sort", signature(x="MyClass"), function(x, ...){}) # Do nothing
Then, I want to calculate a quantile of my class. R's quantile() function internally calls sort(). However, the sort() used inside quantile() is not aware of my S4 method, because it dispatches using UseMethod() (the S3 dispatcher) and not standardGeneric(), the S4 dispatcher. This is demonstrated below:
options(error=traceback)
instance = MyClass()
quantile(instance, 0.5)
This returns a call stack like this:
5: order(x, na.last = na.last, decreasing = decreasing)
4: sort.default(x, partial = unique(c(lo, hi)))
3: sort(x, partial = unique(c(lo, hi)))
2: quantile.default(instance, 0.5)
1: quantile(instance, 0.5)
Since sort.default is being called, it is evident that my custom sort implementation isn't being used.
Is there a simple way to get R to use my S4 method here? I realise I can also define sort.MyClass (the S3 way), but if I do this, what is the point of having an S4 method at all? It seems like S4 is incompatible with core R methods which renders it fairly useless.
Object instance is defined with a slot named x that is numeric. When you call quantile(instance, 0.5), R do not know that you want quantile to act on the slot instance#x.
Approach 1:
MyClass <- setClass("MyClass", slots = list(x = "numeric"))
setMethod(
"quantile",
signature(x = "MyClass"),
function(x, ...) {
callNextMethod(x#x, ...)
}
)
# test drive
instance <- MyClass(x = c(0, 5, 2, 1, 3))
quantile(instance, 0.5)
sort(instance) # error
mean(instance) # error
# see that quantile is now using S4 dispatch
quantile
standardGeneric for "quantile" defined from package "stats"
function (x, ...)
standardGeneric("quantile")
<environment: 0x000001fe1375fe08>
Methods may be defined for arguments: x
Use showMethods(quantile) for currently available ones.
# see method table for quantile
showMethods(quantile, includeDefs = TRUE)
Function: quantile (package stats)
x="ANY"
function (x, ...)
UseMethod("quantile")
x="MyClass"
function (x, ...)
{
callNextMethod(x#x, ...)
}
With this approach, you can see that quantile is automatically converted to using S4 dispatch.
The call quantile(instance, 0.5) is dispatch to quantile,MyClass-method
Inside quantile,MyClass-method, the code callNextMethod(x#x, ...) will dispatch to quantile,ANY-method with content of slot x as argument. This argument is numeric.
Inside quantile,ANY-method, the code will S3 dispatch the calling arguments to quantile.default.
However, This approach require you to specify a customized version of every functions to act on MyClass. Therefore sort(instance) and mean(instance) output error.
Approach 2: Make MyClass as a subclass of numeric. Then all functions that work on numeric will work on MyClass. Below, I add a customized initialize method to automatically sort its numeric argument. A sort,MyClass-method to do no sorting and only return MyClass as numeric for consistency.
MyClass <- setClass("MyClass", contains = "numeric")
setMethod("initialize",
signature(.Object = "MyClass"),
function (.Object, ...)
{
callNextMethod(.Object, sort(..1)) # ..1 is first element of ... see ?dots
}
)
setMethod(
"sort",
signature(x = "MyClass"),
function(x, decreasing = FALSE, ...) {
as(x, "numeric")
}
)
# test drive
instance <- MyClass(c(0, 5, 2, 1, 3))
quantile(instance, 0.5)
quantile(instance)
mean(instance)
sd(instance)
plot(instance)
Note:
setMethod("sort", signature(x="MyClass"), function(x, ...){}) # return NULL
setMethod("sort", signature(x="MyClass"), function(x, ...) x) # return x unchange

How to define a new class method in R?

Defining a new function is straight forward - e.g. myfunct <- function(x) { x * 2}
How can we define a new class method in R, such that obj$newmethod calls method newmethod on the object obj?
Desired output
How can we define a method on obj so that it can be called like so
obj <- 3
obj$newmethod
[1] 6
What I tried so far
newmethod <- function(x) {
x * 2
}
obj$newmethod
# Error in obj$newmethod : $ operator is invalid for atomic vectors
Examples of existing class methods
RSelenium package uses a lot of class methods, for example remDr$closeServer() calls the method closeServer() on the object remDr (which is of class remoteDriver) - there are many more examples under the Fields section of the manual (pages 9 - 13).
1) Reference Classes RSelenium uses Reference Classes which is an OO system that is included with R. Rselenium defines 3 reference classes: errorHandler, remoteDriver and webElement.
In terms of the example in the question we can use the code below. No packages are used in the code here.
For more information on reference classes see ?ReferenceClasses.
# define class, properties/fields and methods
Obj <- setRefClass("Obj",
fields = list(obj = "numeric"),
methods = list(
newmethod = function() 2 * obj
)
)
# instantiate an object of class Obj
obj1 <- Obj$new(obj = 3)
# run newmethod
obj1$newmethod()
## [1] 6
2) proto The example code in the question suggests that you may be looking for an object based (rather than class based) system. In that case the proto package provides one and your example works with a slight change in syntax.
library(proto)
p <- proto(obj = 0, newmethod = function(.) 2 * .$obj)
p$obj <- 3
p$newmethod()
## [1] 6
3) local If you don't need inheritance and other features of object orientation you could just do this. No packages are used.
Obj <- local({
obj <- 0
newmethod <- function() 2 * obj
environment()
})
Obj$obj <- 3
Obj$newmethod()
## [1] 6
4) S3 S3 is included with R and is the most widely used OO system in R; however, it is different than conventional OO systems being based on the ideas of the dylan language so it may not correspond exactly to what you are looking for.
# constructor
obj <- function(x) structure(x, class = "obj")
# method
newmethod <- function(x, ...) UseMethod("newmethod")
newmethod.obj <- function(x, ...) 2 * x
# create object obj3 of class "obj" and apply newmethod to it.
obj3 <- obj(3)
newmethod(obj3)
## [1] 6
5) Other Other OO systems are S4 (included in R), the R6 package and the R.oo package. Also try demo("scoping") for another approach.
For simple cases, you could do
f = function(x, newmethod = 2 * x) {
list(x = x, newmethod = newmethod)
}
obj = f(3)
obj$newmethod

how can I pass argument (names) to a function factory?

I need to build a lot of functions with lots of different arguments, though they otherwise share a lot of code and structure.
To avoid duplication, I thought I'd be clever and build myself a function factory (aka closure).
I can't figure out how to pass the function arguments inside the function factory.
My use case is a bunch of S3 constructor functions, all of which share the same validation mechanism.
So I'll use that as an example to explain my problem.
Say, I have a ClassA and ClassB, each of which require their own arguments in the respective constructor functions:
ClassA <- function(A_arg1, A_arg2) {
# some class-SPECIFIC construction magic happens, say
out <- list(A_arg1, A_arg2)
# some GENERAL construction magic happens
class(out) <- "ClassA"
return(out)
}
ClassB <- function(B_arg1, B_arg2) {
# some class-SPECIFIC construction magic happens, say
out <- B_arg1 + B_arg2
# some GENERAL construction magic happens
class(out) <- "ClassB"
return(out)
}
Obviously, I'd love to avoid the duplication in the general part of the constructor functions, so a function factory that could be used like so would be nice:
ClassA <- produce_class_constructor(classname = "ClassA", fun = function(A_arg1, A_arg2) {return(list(A_arg1, A_arg2))})
This should, ideally, yield the exact same function as the above manually constructed ClassA function, with the general part factored out.
Here's my attempt at building that function factory:
produce_class_constructor <- function(classname, fun) {
class_specific_arguments <- formals(fun = fun) # this works just fine on the console
construct_class <- function(class_specific_arguments) {
# here runs the class-specific stuff
out <- fun(class_specific_arguments)
# here runs the general stuff
class(out) <- classname
}
}
This however, does not work, because the resulting constructor function only has a class_specific_arguments-argument, not the, well, actual A_arg1, and A_arg2.
Is there way to do this?
Am I doing this wrong?
(It's really important to me that the resulting class constructor functions have properly named arguments, so a ... approach won't work).
Here's my attempt:
produce_class_constructor <- function(classname, fun) {
out_fun <- function() {
out_obj <- do.call(fun, as.list(environment()))
class(out_obj) <- classname
out_obj
}
formals(out_fun) <- formals(fun)
out_fun
}
ClassA <- produce_class_constructor(classname = "ClassA",
fun = function(A_arg1, A_arg2) {list(A_arg1, A_arg2)})
ClassA(1, 2)
#[[1]]
#[1] 1
#
#[[2]]
#[1] 2
#
#attr(,"class")
#[1] "ClassA"
ClassB <- produce_class_constructor(classname = "ClassB",
fun = function(B_arg1, B_arg2) {B_arg1 + B_arg2})
ClassB(B_arg2 = 2, 1)
#[1] 3
#attr(,"class")
#[1] "ClassB"
Idea with as.list(environment()) taken from this question. Note that you should be extra careful along that path, as ?formals says, "this is
advanced, dangerous coding".

How to see if an object has a particular method?

I would like to know if a given object has a particular method.
For example, suppose I want to know whether my mystery object has a specific print method. From reading ?methods, I try something like this:
has.print <- function (mysteryObject) {
'print' %in% attr(methods(class=class(mysteryObject)), 'info')$generic
}
m <- lm(Sepal.Length ~ Species, iris)
class(m) # 'lm'
has.print(m)
This is fine if mysteryObject has just one class. If it has multiple, there are problems in methods. I can get around this by using class(mysteryObject)[1], so that (for example)
library(data.table)
class(test) # data.table, data.frame
test <- data.table(iris)
has.print(test) # TRUE since there's a print.data.table
However, if I have something with multiple classes but the first does not have a print method, this returns false. Example:
mlm <- lm(cbind(Petal.Length, Petal.Width) ~ Species, iris)
class(mlm) # 'mlm', 'lm'. Note there is no print.mlm but there's a print.lm
has.print(mlm) # FALSE
This returns FALSE as there is no print.mlm. However, there is a print.lm, that is used instead, so I would like this to return TRUE.
Speaking as someone who knows very little about S3, S4, etc, is there a "proper" way to see if an object has a 'print' method on any of its classes? Ideally this works for both S3 and S4 objects, though I do not know what this means.
I can vectorise my methods(class=...) over class(mysteryObject), but I bet there's a more appropriate way to do it...
Apply methods on every class, unlist and search for "print":
has.print <- function(object) {
"print" %in%
unlist(
lapply(
class(object),
function(x) attr(methods(class = x), "info")$generic)
)
}
It is possible to start from the other side (searching for a class in all
generic print functions):
has.print <- function(object) {
any( sprintf("print.%s", class(object)) %in%
rownames(attr(methods(generic.function = "print"), "info")))
}
To find the method:
which.print <- function(object) {
print_methods <- rownames(attr(methods(generic.function = "print"), "info"))
print_methods[print_methods %in% sprintf("print.%s", class(object))]
}
# > which.print(mlm)
# [1] "print.lm"
S4
S4 classes are "printed" with show method. If no specialized method exists the showDefault is called. Function showMethods will show if there is any specialized show:
For example:
library(Matrix)
showMethods(f = "show", class = "denseMatrix")
#> Function: show (package methods)
#> object="denseMatrix"
showDefault is also calling print for non S4 members.

R: Using toString on a list of objects

Question
How do I make toString output each element of a list the same way as toString outputs a single object?
Explanation
I have created a class an implemented the to.character method for it. It works fine when I use as.character or toString on one object. But if I use those functions on a list of objects I get an uninformative string, which seems to be the output from deparse.
Example code:
setClass("my.class",
slots = c(value = "character"))
my.class <- function(value) {
new("my.class", value = value)
}
setMethod("as.character", "my.class", function (x) {
return(paste0('MyClass(',x#value,')'))
})
obj1 = my.class("val1")
obj2 = my.class("val2")
# desired: MyClass(val1)
# actual: MyClass(val1)
message(toString(obj1))
# desired: MyClass(val1)
# actual: <S4 object of class "my.class">
message(toString(list(obj1)))
# desired: MyClass(val1), MyClass(val2)
# actual: <S4 object of class "my.class">, <S4 object of class "my.class">
message(toString(list(obj1, obj2)))
# FYI:
# outputs: <S4 object of class structure("my.class", package = ".GlobalEnv")>
message(deparse(obj1))
Well, toString is basically a wrapper to paste(). So the problem is how paste(list(obj1, obj2)) runs compared to paste(obj1, obj2). It seems when you pass a list like that, the internal paste() code runs a function called coerceVector which ultimately calls a deparse like function on each of the elements. A seimilar thing happens with S3 classes
paste(list(lm(1:10~rnorm(10)), lm(1:10~rnorm(10))))
What if you defined your own collection class? For example
setClass("my.classes",
slots = c(values = "list"))
setMethod("as.character", "my.classes", function (x) {
sapply(x#values, function(z) as(z, "character"))
})
my.classes <- function(...) {
new("my.classes", values = list(...))
}
list1 <- my.classes(obj1, obj2)
toString(list1)
# [1] "MyClass(val1), MyClass(val2)"
Or maybe you could create a special toString method for lists? This would look like
toString.list <- function(x, ...) {
paste(sapply(x, toString), collapse=", ")
}
then you would call
toString(list(obj1, obj2))
# [1] "MyClass(val1), MyClass(val2)"
The problem would be if any functions depended on the default behavior of toString.default for list objects so it seems a bit less safe, but certainly easier.
And let me clarify that i'm not S4 class expert, but I really can't see a way around the paste() problem after looking at the C code.

Resources