4

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.

Rappster
  • 12,762
  • 7
  • 71
  • 120

1 Answers1

4

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
Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
  • That is so awesome!! Exactly the missing puzzle piece that I was looking for! I did experiment with `selectMethod()`, but that's only like doing the dispatching work manually. But `match.call()` puts it all back to "auto-select-mode", so nice! Thanks a lot, man! – Rappster Mar 11 '14 at 16:20
  • @Rappster Yeah, it's pretty sweet. If you need to perform some even more involved manipulations of the call and its arguments, definitely take a look at the function def of `lm` for further inspiration. – Josh O'Brien Mar 11 '14 at 16:31
  • I think this sort of call manipulation is generally best avoided, but I can't see a better solution here. – hadley Mar 12 '14 at 14:21
  • @hadley Basically agreed. I feel like that call manipulation *shouldn't* be required and suspect that it *isn't*, but still haven't figured out what the correct incantation really *is*. – Josh O'Brien Mar 12 '14 at 15:15
  • Or this might be a bug in S4 dispatch. – hadley Mar 12 '14 at 15:32