S3 style dispatching for S3 objects using formal method definitions
Asked Answered
O

2

7

Related to this question, but slightly different and hopefully more clear.

I am looking for a clean way to formally register methods for both S4 and S3 classes, but without relying on the terrible S3-dot-naming-scheme for dispatching. An example:

setClass("foo");
setClass("bar");

setGeneric("test", function(x, ...){
    standardGeneric("test");
});

setMethod("test", "bar", function(x, ...){
    return("success (bar).");
});

obj1 <- 123;
class(obj1) <- "bar";
test(obj1);

This example shows how we can register a test method for S3 objects of class bar, without the need to name the function test.bar, which is great. However, the limitation is if we register methods this way, they will only be dispatched to the first S3 class of the object. E.g:

obj2 <- 123;
class(obj2) <- c("foo", "bar");
test(obj2);

This doesn't work, because S4 method dispatching will only try class foo and its superclasses. How could this example be extended so that it will automatically select the test method for bar when no appropriate method for foo was found? E.g. S3 style dispatching but without having to go back to naming everything test.foo and test.bar?

So in summary: how to create a generic function that uses formal method dispatching, but in addition fall back on the second, third, etc class of an object for S3 objects with multiple classes.

Oldline answered 24/8, 2012 at 11:53 Comment(0)
A
2

You could write a method

test = function(x, ...) UseMethod("test")

setGeneric("test")

.redispatch = function(x, ...)
{
    if (is.object(x) && !isS4(x) && length(class(x)) != 1L) {
        class(x) = class(x)[-1]
        callGeneric(x, ...)
    } else callNextMethod(x, ...)
}

setMethod(test, "ANY", .redispatch)

But I personally wouldn't mix S3 and S4 in this way.

Agglutinative answered 25/8, 2012 at 12:14 Comment(2)
Yes I have been doing something similar; however the fact that you are actually changing the class attribute of x along the way can have undesirable side effects... Is there any way you could dispatch it to the next method without modifying the object itself?Oldline
I don't have an answer for you; I think that the S4 method for 'bar' working on an S3 class 'bar' is an accident of implementation, and you're building a complex structure on very shaky foundations.Agglutinative
S
3

?setOldClass will give the answer:

setOldClass(c("foo", "bar"))

setGeneric("test", function(x, ...)standardGeneric("test"))
setMethod("test", "bar", function(x, ...)return("success (bar)."))
Spitfire answered 24/8, 2012 at 16:19 Comment(1)
So this is exactly not what I want. It requires a formal inheritance between classes "foo" and "bar", which might not be the case. I want it to work for any arbitrary object with class=[x, "bar"] for x = anything, without having to declare inheritance between x and "bar" for every possible class x. Suppose someone else has created an object with classes ["zoo", "bar"], then test() function should be smart enough to select the method for "bar".Oldline
A
2

You could write a method

test = function(x, ...) UseMethod("test")

setGeneric("test")

.redispatch = function(x, ...)
{
    if (is.object(x) && !isS4(x) && length(class(x)) != 1L) {
        class(x) = class(x)[-1]
        callGeneric(x, ...)
    } else callNextMethod(x, ...)
}

setMethod(test, "ANY", .redispatch)

But I personally wouldn't mix S3 and S4 in this way.

Agglutinative answered 25/8, 2012 at 12:14 Comment(2)
Yes I have been doing something similar; however the fact that you are actually changing the class attribute of x along the way can have undesirable side effects... Is there any way you could dispatch it to the next method without modifying the object itself?Oldline
I don't have an answer for you; I think that the S4 method for 'bar' working on an S3 class 'bar' is an accident of implementation, and you're building a complex structure on very shaky foundations.Agglutinative

© 2022 - 2024 — McMap. All rights reserved.