7

Is there a way to define what happens when the argument of a method is missing or NULL?

In the example below, I'd like to call the same function no matter whether I type foo() or foo(NULL). Of course I know that I can have a method setMethod("foo","NULL",function(x) foo()) but that that's code replication and a possible source of mistakes.

Thanks!

setGeneric("foo",function(x) standardGeneric("foo"))
setMethod("foo","numeric",function(x) "numeric")
setMethod("foo","NULL",function(x) "NULL")
setMethod("foo","missing",function(x) "missing")

R> foo(1)
[1] "numeric"
R> foo()
[1] "missing"
R> foo(NULL)
[1] "NULL"
Florian Bw
  • 746
  • 1
  • 7
  • 16

3 Answers3

13

Almost exactly three years late to the party, but you really want setClassUnion:

> setClassUnion("missingOrNULL", c("missing", "NULL"))
> setGeneric("foo",function(x) standardGeneric("foo"))
> setMethod("foo","numeric",function(x) "numeric")
> setMethod("foo","missingOrNULL",function(x) "NULL")

> foo(1)
[1] "numeric"
> foo()
[1] "NULL"
> foo(NULL)
[1] "NULL"

setClassUnion creates a virtual class that is a superclass (parent) to the component classes, so then both children inherit from that class, which means you can dispatch the same function against each child.

BrodieG
  • 51,669
  • 9
  • 93
  • 146
3

Using setMethod("foo","NULL",function(x) foo()) is not a code replication, since you don't replicate code but put a call to it. I would say it is a very good way of solving your problem.

Joris Meys
  • 106,551
  • 31
  • 221
  • 263
mbq
  • 18,510
  • 6
  • 49
  • 72
  • @mbq : I know, I changed the wrong function :-) added a space to your post so I could upvote. – Joris Meys Dec 14 '10 at 16:05
  • Ok, it does not replicate 'intelligent' code, but still replicates stupid code. It is prone to introduce bugs. I have methods with 10 or 15 arguments. Using an extra setMethod for each possible combination of NULL/missing of some of those arguments is not a good choice in my opinion. – Florian Bw Dec 15 '10 at 08:43
  • @Florian How would you than deal with the ambiguity of judging which argument goes where and which is missing? – mbq Dec 15 '10 at 14:08
  • @mbq : by using "ANY" in the signature? ;-) – Joris Meys Dec 15 '10 at 14:14
  • @Joris Not about implementation, but in general. Like `foo(x,y,z)` is `foo(x,NULL,NULL,y,NULL,z,NULL,NULL)` or `f(x,NULL,NULL,NULL,NULL,NULL,y,z)`? – mbq Dec 15 '10 at 14:59
  • @mbq : if a user gives unnamed arguments, one cannot expect that the code runs without errors. Hence, in those cases I consider the error to be at the users side. – Joris Meys Dec 15 '10 at 15:41
2

I guess the appropriate way is to use "ANY" in the signature:

setGeneric("foo",function(x) standardGeneric("foo"))
setMethod("foo","numeric",function(x) "numeric")
setMethod("foo","ANY",function(x) "ANY")

> foo(1)
[1] "numeric"

> foo()
[1] "ANY"

> foo(NULL)
[1] "ANY"

Make sure you specify every other possibility that you want taken care of, as "ANY" also takes all the rest that doesn't fit to the signature of another method.

If you have arguments that can be missing, you can just not specify them in the signature of setMethods and set a default in the generic. This is -in my humble view- a better design choice.

setGeneric("foo",function(x,y=NULL,...) {
        standardGeneric("foo")
    })

setMethod("foo",c("numeric","ANY"),function(x,y,...) {
            print(y)
    })
setMethod("foo",c("numeric","numeric"),function(x,y,...) {
            x + y
    })

> foo(1)
NULL

> foo(1,3)
[1] 4

> foo(1,NULL)
NULL

Now you can deal with the NULL cases in-code like you would do with the missing arguments.

On a sidenote: Now I added NULL as default value, but in many cases there are far more sensible choices for default values. Just remember that the setMethod takes the initial signature, and that when y is set as NULL, this is not replaced by the default.

eg:

setGeneric("bar",function(x,y=2,...) {
        standardGeneric("bar")
    })

setMethod("bar",c("numeric","ANY"),function(x,y,...) {
            x + y
    })
setMethod("bar",c("numeric","numeric"),function(x,y,...) {
            x - y
    })

> bar(1)
[1] 3

> bar(1,2)
[1] -1

> bar(1,NULL) # this y is not replaced with the default!
numeric(0)

DIRTY HACK :

I find the approach a bit awkward, but here is a dirty hack that sets all missing parameters to NULL :

setGeneric("foo",function(x,y,z) {
    pars <- names(formals(foo))
    for(i in pars){
        tryerr <- try(get(i),silent=T)
        if(is(tryerr,"try-error")){ assign(i,NULL)}
    }
    standardGeneric("foo")
}

Trying this, you get :

> foo(1)
[1] "numeric"

> foo(NULL)
[1] "NULL"

> foo()
[1] "NULL"

So you never dispatch to the missing any more. You can just forget about it. But this ain't the appropriate way of doing things...

Joris Meys
  • 106,551
  • 31
  • 221
  • 263
  • That's the solution I am using now. I'd like to be more specific than any, but I guess there's no good way? – Florian Bw Dec 15 '10 at 16:21
  • @Florian: I added a piece of code that can make you be more specific than "any", by changing the arguments so they all dispatch to the NULL methods, but this is definitely a dirty hack. Why not set all the defaults to NULL, use "ANY", and deal with the NULL-cases appropriately in your code? That still seems the cleanest way of doing it. – Joris Meys Dec 15 '10 at 16:42