S4 classes - overload a method with a variable number of arguments - r

I would like to have a variable number of arguments in my S4 generic myMethod such that these are both valid:
myMethod(100)
myMethod(100, 200)
Here is my attempt at a definition:
setGeneric(
"myMethod",
function(x) {
standardGeneric("myMethod")
})
setMethod(
"myMethod",
signature = c("numeric"),
definition = function(x) {
print("MyMethod on numeric")
})
setMethod(
"myMethod",
signature = c("numeric", "numeric"),
definition = function(x, y) {
print("MyMethod on numeric, numeric")
})
However, this gives the error:
Error in matchSignature(signature, fdef) :
more elements in the method signature (2) than in the generic signature (1) for function ‘myMethod’

It's worth clarifying whether you want to dispatch (select a method) on more than one argument (in which case include all argument names in the signature= of setGeneric())
setGeneric("fun", function(x, y) standardGeneric("fun"),
signature=c("x", "y"))
setMethod("fun", c(x="numeric", y="numeric"), function(x, y) {
"fun,numeric,numeric-method"
})
versus dispatching based on the first (include only the first argument in signature=) and either require all methods to have additional arguments (name the arguments in the generic function)
setGeneric("fun", function(x, y) standardGeneric("fun"),
signature="x")
setMethod("fun", c(x="numeric"), function(x, y) {
"fun,numeric-method"
})
or only some methods (use ... in the generic, and name the arguments in the method).
setGeneric("fun", function(x, ...) standardGeneric("fun"),
signature="x")
setMethod("fun", c(x="numeric"), function(x, y) {
"fun,numeric-method"
})

Your generic should support 2 arguments.
setGeneric(
"myMethod",
function(x, y) {
standardGeneric("myMethod")
})
Also the function in your second method should actually support two arguments:
setMethod(
"myMethod",
signature = c("numeric", "numeric"),
definition = function(x, y) {
print("MyMethod on numeric, numeric")
})
More generally, if you want to specify arbitrarily many arguments, you should have a look at the elipsis argument ....

Related

`[.` method for `ReferenceClass`

I would like to write a [. method for my ReferenceClass. So far, I have something like this:
DT <- data.table(Index=1:5)
MySeries <- setRefClass("MySeries", fields = list(data="data.table"))
setMethod("[","MySeries",function(x, i,j,drop) {
ii <- substitute(i)
x$data <- x$data[eval(ii)]
return(x)
})
S <- MySeries(data=DT)
... but it throws an error when I finally call S[Index>3]. How to fix the above to get this expected result?
Index
4: 4
5: 5
This is really about the use of eval(substitute()) as much as about S4 methods. Here is the generic that you are interested in
> getGeneric("[")
standardGeneric for "[" defined from package "base"
function (x, i, j, ..., drop = TRUE)
standardGeneric("[", .Primitive("["))
<bytecode: 0x42f4fe0>
<environment: 0x3214270>
Methods may be defined for arguments: x, i, j, drop
Use showMethods("[") for currently available ones.
Your method signature differs from the generic (no '...' and no default for 'drop') so the method has a nested '.local' function
> getMethod("[", "MySeries")
Method Definition:
function (x, i, j, ..., drop = TRUE)
{
.local <- function (x, i, j, drop)
{
ii <- substitute(i)
x$data <- x$data[eval(ii)]
return(x)
}
.local(x, i, j, ..., drop)
}
Signatures:
x
target "MySeries"
defined "MySeries"
and subsitute(i) is not what you think it is. Instead, write a method matching the generic signature
setMethod("[", "MySeries", function(x, i, j, ..., drop=TRUE) {
x$data <- x$data[eval(substitute(i))]
x
})
nested functions are a general problem with the eval(substitute()) paradigm, not just definition of S4 methods; see this question.

Method dispatch with missing arguments

How can I avoid the classic Error: argument "<argname>" is missing, with no default error (see example below) when explicitly dispatching argument values to subsequent S4 methods in a given S4 method.
Example
Big picture
Whe have a method foo() that calls method bar().
Both methods depend on arguments x and y.
Method foo() dispatches arguments x and y to bar() in an explicit way: bar(x=x, y=y).
Now, the crucial point here is that I don't want foo() to care if any or all of the arguments that are passed along to bar() are missing.
Generic methods
setGeneric(
name="foo",
signature=c("x", "y"),
def=function(x, y, ...) {
standardGeneric("foo")
}
)
setGeneric(
name="bar",
signature=c("x", "y"),
def=function(x, y, ...) {
standardGeneric("bar")
}
)
Methods for bar()
setMethod(
f="bar",
signature=signature(x="missing", y="missing"),
definition=function(x, y, ...) {
print("Doing what I'm supposed to do when both args are missing")
return(NULL)
}
)
setMethod(
f="bar",
signature=signature(x="ANY", y="missing"),
definition=function(x, y, ...) {
message("'y' is missing, but I can give you 'x':")
print(x)
return(NULL)
}
)
setMethod(
f="bar",
signature=signature(x="missing", y="ANY"),
definition=function(x, y, ...) {
message("'x' is missing, but I can give you 'y':")
print(y)
return(NULL)
}
)
setMethod(
f="bar",
signature=signature(x="ANY", y="ANY"),
definition=function(x, y, ...) {
message("x:")
print(x)
message("y:")
print(y)
return(NULL)
}
)
Method for foo()
As mentioned above, I don't want foo() to care if any or all of the arguments that are passed along to bar() are missing. It is just supposed to pass everything along to bar() in an explicit manner:
setMethod(
f="foo",
signature=signature(x="ANY", y="ANY"),
definition=function(x, y, ...) {
bar(x=x, y=y)
}
)
The method def might look good on first sight, but it will fail if either x or y are missing when calling it:
> foo(x="Hello", y="World!")
x:
[1] "Hello"
y:
[1] "World!"
NULL
> foo(x="Hello")
Error in bar(x = x, y = y) :
error in evaluating the argument 'y' in selecting a method for function 'bar': Error: argument "y" is missing, with no default
> foo()
Error in bar(x = x, y = y) :
error in evaluating the argument 'x' in selecting a method for function 'bar': Error: argument "x" is missing, with no default
Workaround
This is the only workaround that I could come up with so far:
setMethod(
f="foo",
signature=signature(x="ANY", y="ANY"),
definition=function(x, y, ...) {
if (missing(x) && missing(y)) {
bar()
} else if (missing(x)) {
bar(y=y)
} else if (missing(y)) {
bar(x=x)
} else {
bar(x=x, y=y)
}
}
)
> foo(x="Hello", y="World!")
x:
[1] "Hello"
y:
[1] "World!"
NULL
> foo(x="Hello")
'y' is missing, but I can give you 'x':
[1] "Hello"
NULL
> foo(y="World!")
'x' is missing, but I can give you 'y':
[1] "World!"
NULL
> foo()
[1] "Doing what I'm supposed to do when both args are missing"
NULL
It works, but I don't really like it because of all the if ... else statements. The whole "if-else logic" already went in the specification of the various methods for bar(). After all, that's the whole point of having a method dispatcher in the first place, right? Hence I would consider the statements as "undesired work" and I'm looking for a better way.
One could of course resort to to using NULL as default value for all "critical" arguments, but I would like to rely on missing() instead of is.null() in my functions as much as possible.
Here is an alternative idea. (It's broadly inspired by the sort of "computing on the language" used by many of R's model-fitting functions.)
setMethod(
f="foo",
signature=signature(x="ANY", y="ANY"),
definition=function(x, y, ...) {
mc <- match.call()
mc[[1]] <- quote(bar)
eval(mc)
}
)
foo(x="Hello")
# 'y' is missing, but I can give you 'x':
# [1] "Hello"
# NULL
foo(y="World")
# 'x' is missing, but I can give you 'y':
# [1] "World"
# NULL
foo()
# [1] "Doing what I'm supposed to do when both args are missing"
# NULL

Using do.call for generic S4 method

I have a list of parameters and would like to call B(var3=list(1:3)) via do.call. But the following example only calls the method for dispatch "missing". How do I access the method for dispatch "ANY"?
B.initialize<-function(..., var3=list()){
callSuper(..., var3=as.list(var3))
}
.B<-setRefClass(Class = "B"
,fields = list(var3 = "list")
,methods = list(initialize=B.initialize))
setGeneric("B", function(x, ...) standardGeneric("B"))
setMethod("B", "missing", function(x, ...) {
.B()
})
setMethod("B", "ANY", function(x, ...) {
.Part(var1=x, ...)
})
do.call(B,list(var3=list(1:3)))

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! :)

Dispatching an argument's default value from an S4 generic function to its associated methods

Suppose all of your S4 methods associated to a specific S4 generic function/method share a formal argument that is supposed to have a specific default value. Intuitively, I would state such an argument in the definition of the S4 generic (as opposed to stating it in each method definition which would seem somewhat redundant to me).
However, I noticed that this way I'm running into trouble as it seems that the default value of the formal argument is not dispatched to the methods and thus an error is thrown.
Isn't this somewhat against the idea of having a combination of a generic and methods? Why would I have to state the formal argument in each method separately again when the default value is always the same? Can I explicitly dispatch formal arguments' default values somehow?
Below you'll find a short illustration of the behavior
Generic function
setGeneric(
name="testFoo",
signature=c("x", "y"),
def=function(
x,
y,
do.both=FALSE,
...
) {
standardGeneric("testFoo")
}
)
Method
setMethod(
f="testFoo",
signature=signature(x="numeric", y="numeric"),
definition=function(
x,
y
) {
if (do.both) {
out <- list(x=x, y=y)
} else {
out <- x
}
return(out)
}
)
Error
> testFoo(x=1, y=2)
Error in .local(x, y, ...) : object 'do.both' not found
Redundant statement of do.both fixes it
setMethod(
f="testFoo",
signature=signature(x="numeric", y="numeric"),
definition=function(
x,
y,
do.both=FALSE
) {
if (do.both) {
out <- list(x=x, y=y)
} else {
out <- x
}
return(out)
}
)
> testFoo(x=1, y=2)
[1] 1
When you call testFoo(x=1, y=2), it is processed first by the S4 generic, which looks for a method, finds it, and dispatches to it a call that looks like this: testFoo(x=1, y=2, do.both=FALSE, ...).
In the words of ?standardGeneric:
‘standardGeneric’ dispatches the method defined for a generic
function named ‘f’, using the actual arguments in the frame from
which it is called.
If the method to which it dispatches that call does not take a do.both argument, the method --- just like any other R function --- throws an error. No function can process a call containing an argument foo unless it's function definition contains either (a) a formal argument foo or (b) a "dots" argument, ..., which can absorb arbitrary supplied arguments.
Basically what you've tried is no different than the following, which fails in a similarly but perhaps easier-to-see way:
testFooGeneric <- function(x=1, y=2, do.both=FALSE, ...) {
## The line below does essentially what standardGeneric() does
if(is.numeric(x) & is.numeric(y)) {
testFooMethod(x=x, y=y, do.both=do.both)
}
}
testFooMethod <- function(x, y) {
cat("Success!\n")
}
testFooGeneric(x=1, y=2)
# Error in testFooMethod(x = x, y = y, do.both = do.both) :
# unused argument(s) (do.both = do.both)
To fix the above, you need to redefine testFooMethod() in one of the following two ways, either of which will also remedy your S4 method:
## Option 1
testFooMethod <- function(x, y, do.both) {
cat("Success!\n")
}
testFooGeneric(x=1, y=2)
# Success!
## Option 2
testFooMethod <- function(x, y, ...) {
cat("Success!\n")
}
testFooGeneric(x=1, y=2)
## Success!

Resources