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
Related
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 ....
let's assume you have one S4 class "A", and a subclass "B" which has additional features. Each have their own validity checks in place - B should only check the additional features. Now in the initialization of B, I would like to start out from an object of class A, and then amend it with the additional features. However, this creates problems, and I guess I am somewhere violating R's assumptions in this example.
Here's the dummy code:
setClass(Class="A",
representation=
representation(x="numeric"),
validity=
function(object){stopifnot(x > 0)})
setMethod("initialize",
signature(.Object="A"),
function(.Object,
...,
z){
x <- get("z") + 1
callNextMethod(.Object,
...,
x=x)
})
setClass(Class="B",
contains="A",
representation=
representation(y="numeric"),
validity=
function(object){stopifnot(y > 0)})
setMethod("initialize",
signature(.Object="B"),
function(.Object,
...,
bla){
.Object <- callNextMethod(.Object,
...)
.Object#y <- .Object#x + bla
return(.Object)
})
test <- new("B",
z=4,
bla=5)
If I try to create the "test" object, I get:
Error in stopifnot(x > 0): object 'x' not found
Do you know how I could do better?
Thanks a lot in advance!
Best regards
Daniel
A convenient test of the assumptions in S4 is that new() called with no arguments on a non-VIRTUAL class needs to return a valid object. Your class does not pass this test
> validObject(new("A"))
Error in get("z") : argument "z" is missing, with no default
One option would provide a default value to z in the initialize method, or (my preference) to use a prototype in the class definition coupled with a constructor. Also the validity function is supposed to return TRUE (if valid) or a character vector describing how it is not valid. So I wrote your class 'A' as
.A <- setClass(Class="A",
representation(x="numeric"),
prototype(x=1),
validity= function(object) {
msg <- NULL
if (length(object#x) != 1 || object#x <= 0)
msg <- c(msg, "'x' must be length 1 and > 0")
if (is.null(msg)) TRUE else msg
})
(the return value of setClass() just wraps new() in a more semantically rich function call).
> validObject(.A())
[1] TRUE
Instead of using the initialize method (which is tricky to implement correctly -- it's a copy constructor as well) I'd write
A <- function(z, ...)
.A(x=z+1, ...)
which behaves as expected
> A()
Error in initialize(value, ...) (from valid.R!7685pfr#2) :
argument "z" is missing, with no default
> A(1)
An object of class "A"
Slot "x":
[1] 2
I think the extension of these principles to "B" should be straight-forward, and a good "exercise for the reader"!
Just to complete Martin's answer, here is the full solution to my problem:
.A <- setClass(Class="A",
representation(x="numeric"),
prototype(x=1),
validity=
function(object){
msg <- NULL
if (length(object#x) != 1 || object#x <= 0)
msg <- c(msg, "'x' must be length 1 and > 0")
if (is.null(msg)) TRUE else msg
})
validObject(.A())
A <- function(z, ...)
{
x <- z + 1
.A(x=x, ...)
}
.B <- setClass(Class="B",
representation(y="numeric"),
prototype(y=2),
contains="A",
validity=
function(object){
msg <- NULL
if (length(object#y) != 1 || object#y <= 0)
msg <- c(msg, "'y' must be length 1 and > 0")
if (is.null(msg)) TRUE else msg
})
validObject(.B())
B <- function(bla, z, ...)
{
obj <- A(z, ...)
y <- obj#x + bla
.B(obj, y=y, ...)
}
test <- B(z=4,
bla=5)
Thanks again Martin for the extremely fast and perfect help!
best regards
Daniel
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.
I'm astonished that missing seems not working in a function called by lapply. Assume I have the following functions:
.add <- function(x, arg, ...) {
if (missing(arg)) {
arg <- 1
}
print(match.call())
return(x + arg)
}
wrapper <- function(l, arg, ...) {
return(lapply(l, .add, arg=arg, ...))
}
Setting arg explicit works like excepted:
wrapper(list(x=1:10, y=1:10), arg=1)
#FUN(x = X[[1L]], arg = ..1)
#FUN(x = X[[2L]], arg = ..1)
#$x
# [1] 2 3 4 5 6 7 8 9 10 11
#
#$y
# [1] 2 3 4 5 6 7 8 9 10 11
Without arg I would expect the same output but it fails:
wrapper(list(x=1:10, y=1:10))
#FUN(x = X[[1L]], arg = ..1)
# Error in FUN(X[[1L]], ...) : argument "arg" is missing, with no default
missing works in nested wrapper functions where no lapply is used.
Why it seems to have no effect in functions called by lapply?
EDIT: Default arguments also don't work:
.add <- function(x, arg=5, ...) {
if (missing(arg)) {
arg <- 1
}
print(match.call())
return(x + arg)
}
wrapper(list(x=1:10, y=1:10))
#FUN(x = X[[1L]], arg = ..1)
# Error in FUN(X[[1L]], ...) : argument "arg" is missing, with no default
It seems that arg is neither missing nor accessible. What happens here?
(I know that I could circumvent this by setting arg=NULL in wrapper and if (is.null(arg)) in .add or something else. .add is an internal function which determines arg by its own based on the input (e.g. arg=mean(x)) and I want arg in the wrapper to document the argument arg for the user and to allow the user to overwrite the default behavior. And most important: I want to understand why this is not working!)
EDIT2: Finally this behaviour is fixed. It was a bug in R < 3.2.0, see PR#15707.
First, I'll mention that I believe the idiomatic way of doing this is by constructing a call and then evaluating it. See write.csv for an example. I believe this code will do what you want, using that method.
wrapper <- function(X, arg, ...) {
force(X) # optional; if X is missing, the error message will be more informative
Call <- match.call(expand.dots=TRUE)
Call[[1L]] <- as.name("lapply")
Call$FUN <- as.name(".add")
eval.parent(Call)
}
Ok, now here's an attempt to explain the issues you discovered. I stand ready to be corrected as well, but hopefully this will at least help clarify the issues, just like #idfah's answer did.
First, I'll tackle the "defaults" issue, as I think it's more straightforward. This one I think can be made simpler, as in the following two functions, where the second (f2) simply calls the first (f1). What we see is that the default argument in f1 gets overridden by the promise to x in f2, and when that promise is evaluated, it is missing. Moral of this story (I think); defaults must be set again in your calling function, if that variable is included in the call.
f1 <- function(x=1) {print(match.call()); x}
f2 <- function(x) {f1(x=x)}
f1()
## f1()
## [1] 1
f2()
## f1(x = x)
## Error in f1(x = x) : argument "x" is missing, with no default
Now on to the missing in lapply issue. Here I basically have sgibb's code, but have added a message about whether or not arg is considered missing. We have what seems to be a curious contradiction; the message tells us that arg is NOT missing, but when the function tries to access it, we get an error message telling us that arg IS missing.
.add <- function(x, arg) {
print(match.call())
if(missing(arg)) {
message("arg is missing in .add")
x
} else {
message("arg is not missing")
x + arg
}
}
wrapper <- function(l, arg) {lapply(l, .add, arg=arg)}
wrapper(1)
## FUN(x = 1[[1L]], arg = ..1)
## arg is not missing
## Error in FUN(1[[1L]], ...) : argument "arg" is missing, with no default
What I think is happening is the lapply is putting the promise to arg in ..1, so it doesn't look missing, but when it tries to evaluate it, it finds that it is missing. Moral of this story (I think); don't try to propagate missings through lapply.
UPDATE: More precisely, it's something with how dot expansion works. Consider this version of lapply (which doesn't actually work on a list, but otherwise has the same code style); this shows we get the same behavior.
apply3 <- function(X, FUN, ...) {
print(match.call())
FUN(X, ...)
}
wrapper3 <- function(l, arg) {apply3(l, .add, arg=arg)}
wrapper3(1)
## apply3(X = l, FUN = .add, arg = arg)
## FUN(x = X, arg = ..1)
## arg is not missing
## Error in FUN(X, ...) : argument "arg" is missing, with no default
But when we substitute the dots with a variable name, it works as expected.
apply4 <- function(X, FUN, hm) {
print(match.call())
FUN(X, hm)
}
wrapper4 <- function(l, arg) {apply4(l, .add, hm=arg)}
wrapper4(1)
## apply4(X = l, FUN = .add, hm = arg)
## FUN(x = X, arg = hm)
## arg is missing in .add
## [1] 1
And one more example; if I use dots, but do the expansion myself, by calling ..1 directly, it also works! This is curious as the matched call is the same as the version that doesn't work.
apply3b <- function(X, FUN, ...) {
print(match.call())
FUN(X, ..1)
}
wrapper3b <- function(l, arg) {apply3b(l, .add, arg=arg)}
wrapper3b(1)
## apply3b(X = l, FUN = .add, arg = arg)
## FUN(x = X, arg = ..1)
## arg is missing in .add
## [1] 1
There is no missing in your wrapper, so it bombs there. In this case, you don't need it since you are using variadic arguments anyway. Try this:
.add <- function(x, arg, ...) {
if (missing(arg))
arg <- 1
print(match.call())
return(x + arg)
}
wrapper <- function(l, ...)
return(lapply(l, .add, ...))
If the wrapper needs to know arg, then you need an missing there:
.add <- function(x, arg, ...) {
print(match.call())
return(x + arg)
}
wrapper <- function(l, ...) {
if (missing(arg))
arg <- 1
return(lapply(l, .add, arg=arg, ...))
}
I stand corrected
The following example allows the missing to be at the bottom of the call stack, presumably because of lazy evaluation. I am unsure then why your example does not work... curious.
wrapper.c <- function(l, arg)
{
if (missing(arg))
arg <- 1
print("I'm in c")
arg
}
wrapper.b <- function(l, arg)
{
print("I'm in b")
wrapper.c(l, arg)
}
wrapper.a <- function(l, arg)
wrapper.b(l, arg)
> wrapper.a(1)
[1] "I'm in b"
[1] "I'm in c"
[1] 1
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!