R: Set function as generic for some class - r

I wrote a function that returns an object of class myClass. Specific to this class, I have a function tryMe.myClass() that I'd like to be generic to myClass, such that I only have to call tryMe(object, x) instead of tryMe.myClass(object, x), where tryMe(object, x) will only work of object is of class myClass.
Both functions (the constructor and tryMe.myClass()) have their own .R-File inside a package I created.
What do I need to modify for this to work?
Much thanks!
Stefan

Just define a generic as in line ## below
tryMe <- function(object, ...) UseMethod("tryMe") ##
tryMe.myClass <- function(object, x, ...) "ok"
# test
obj <- structure(NA, class = "myClass")
tryMe(obj, 3)
## [1] ok

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

R: how to find what S3 method will be called on an object?

I know about methods(), which returns all methods for a given class. Suppose I have x and I want to know what method will be called when I call foo(x). Is there a oneliner or package that will do this?
The shortest I can think of is:
sapply(class(x), function(y) try(getS3method('foo', y), silent = TRUE))
and then to check the class of the results... but is there not a builtin for this?
Update
The full one liner would be:
fm <- function (x, method) {
cls <- c(class(x), 'default')
results <- lapply(cls, function(y) try(getS3method(method, y), silent = TRUE))
Find(function (x) class(x) != 'try-error', results)
}
This will work with most things but be aware that it might fail with some complex objects. For example, according to ?S3Methods, calling foo on matrix(1:4, 2, 2) would try foo.matrix, then foo.numeric, then foo.default; whereas this code will just look for foo.matrix and foo.default.
findMethod defined below is not a one-liner but its body has only 4 lines of code (and if we required that the generic be passed as a character string it could be reduced to 3 lines of code). It will return a character string representing the name of the method that would be dispatched by the input generic given that generic and its arguments. (Replace the last line of the body of findMethod with get(X(...)) if you want to return the method itself instead.) Internally it creates a generic X and an X method corresponding to each method of the input generic such that each X method returns the name of the method of the input generic that would be run. The X generic and its methods are all created within the findMethod function so they disappear when findMethod exits. To get the result we just run X with the input argument(s) as the final line of the findMethod function body.
findMethod <- function(generic, ...) {
ch <- deparse(substitute(generic))
f <- X <- function(x, ...) UseMethod("X")
for(m in methods(ch)) assign(sub(ch, "X", m, fixed = TRUE), "body<-"(f, value = m))
X(...)
}
Now test it. (Note that the one-liner in the question fails with an error in several of these tests but findMethod gives the expected result.)
findMethod(as.ts, iris)
## [1] "as.ts.default"
findMethod(print, iris)
## [1] "print.data.frame"
findMethod(print, Sys.time())
## [1] "print.POSIXct"
findMethod(print, 22)
## [1] "print.default"
# in this example it looks at 2nd component of class vector as no print.ordered exists
class(ordered(3))
## [1] "ordered" "factor"
findMethod(print, ordered(3))
## [1] "print.factor"
findMethod(`[`, BOD, 1:2, "Time")
## [1] "[.data.frame"
I use this:
s3_method <- function(generic, class, env = parent.frame()) {
fn <- get(generic, envir = env)
ns <- asNamespace(topenv(fn))
tbl <- ns$.__S3MethodsTable__.
for (c in class) {
name <- paste0(generic, ".", c)
if (exists(name, envir = tbl, inherits = FALSE)) {
return(get(name, envir = tbl))
}
if (exists(name, envir = globalenv(), inherits = FALSE)) {
return(get(name, envir = globalenv()))
}
}
NULL
}
For simplicity this doesn't return methods defined by assignment in the calling environment. The global environment is checked for convenience during development. These are the same rules used in r-lib packages.

S3 Method dispatch for ... or equivalent

How do I define a method for a list of objects of the same class?
Eg.
foo <- letters[1:5]
foo2 <- letters[6:10]
class(foo) <- "abc"
class(foo2) <- "abc"
new_method <- function(...) {UseMethod("new_method", ...)}
new_method.abc <- function(...) {do.call("c", list(...))}
# use results in error
new_method(foo,foo2)
Error in new_method(foo, foo2) : '...' used in an incorrect context
Here I want ... to be an arbitrary length list of objects all of which have the same class and I want to do something with them (combine them, specific to my real class's use case).
It makes sense to me that ... doesn't have a class that can be sent to method dispatch; but a simple re-write also doesn't work since new_method.list doesn't / shouldn't exist
new_method <- function(...) {UseMethod("new_method", list(...))}
new_method(foo,foo2)
Error in UseMethod("new_method", list(...)) :
no applicable method for 'new_method' applied to an object of class "list"

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.

setMethod in S4 - how to define a method that doesn't need the object as an argument?

In R with S4 classes, I define a method to assign two values to an object:
setGeneric("setValues", function(object, x, y) {
standardGeneric("setValues")
})
setMethod(f = "setValues", signature = "chart", definition = function(object, x, y) {
object#x <- x
object#y <- y
return(object)
})
and then use it as follows
obj <- setValues(obj, "value_X", "value_Y")
But, this means that I have to write obj as an argument to the function all the time.
Is it possible to define a method in S4 that could be used without the object itself as an argument?
For example:
obj <- setValues("value_X", "value_Y")
I am very grateful for any kind of suggestions! :)

Resources