3

I am developing a package and want to write two subset methods for objects of a custom class, myclass, with dispatch on two arguments, first one being the object to subset, of class myclass, and the second being either logical of character vector, like so:

setMethod(
    f = "subset",
    signature = c(x = "myclass", subset = "logical"),
    definition = function(x, subset){
            # function body 
    }
)

setMethod(
    f = "subset",
    signature = c(x = "myclass", subset = "character"),
    definition = function(x, subset){ 
        # different function body 
    }
)

However, I cannot do this because the S3 generic dispatches on one argument only. And I don't want to create a new generic for subset because it would mask the existing generic when my package is loaded. One way around this issue, I think, would be to create a generic and methods of different name, but this would not be very intuitive for the users, right? So am I missing/misunderstanding something, and is there any witty way to have multiple dispatch for S3 generics?

anamaria
  • 362
  • 3
  • 11
  • You might want "S4" or perhaps Reference Classes (one good ref: http://adv-r.had.co.nz/OO-essentials.html). – r2evans Mar 18 '18 at 13:15
  • Otherwise, you might need to handle the second argument type internally. – r2evans Mar 18 '18 at 13:17
  • So there is no way one can write S4 methods for S3 generic? Thank you for the suggestion, I haven't thought to handle this within the method, but that's certainly a way to work around the issue. – anamaria Mar 18 '18 at 14:52
  • S3 triggers on the first argument. Period. If you want more than that, you have to choose something other than S3. S4 provides for more control, as do Reference Classes and R6. Within the method is effectively re-implementing some of why S4 et al were generated, and at times it might be the least-painful option. If you think there is expansion in the future, I might suggest leaning towards the more robust alternatives; if this is almost certainly a one-off and the maintenance needs down the road very limited, then perhaps internal-implementation will suffice. – r2evans Mar 18 '18 at 22:22
  • @anamaria hey there! Emulating multiple dispatch with S3 has long been something I wanted to "get as right as possible". Do you have any updates on what worked best for you? Also found this [gist](https://gist.github.com/wch/adf13fd291976d6bf312) by Winston Chang – Rappster Sep 09 '19 at 07:51
  • @Rappster I found the approach suggested in the JDL's answer sufficient for my situation, and so far I haven't come across anything that would be more appropriate. – anamaria Sep 13 '19 at 12:59
  • @anamaria thanks for getting back to me on this. While I overall like the level of sophistication of S4, I don't want to leave S3 world. I actually came up with an [approach](https://stackoverflow.com/questions/57903831/emulating-multiple-dispatch-in-s3-for-3-signature-arguments-via-sequential-appr) of my own. Far from nice, but it works and it's S3 ;-) – Rappster Sep 13 '19 at 13:05

1 Answers1

2

Normally in this situation you would set subset as an S4 generic but since you have reasons for not wanting to do that, you can get around this by defining a separate generic and calling it from within the S3 method, along the lines of

mySubset <- function(x,subset){
  stop("this is only a generic function: it should never be called!")
}
setGeneric("mySubset")
## methods for mySubset
setMethod(
    f = "mySubset",
    signature = c(x = "myclass", subset = "logical"),
    definition = function(x, subset){
            # function body 
    }
)

setMethod(
    f = "mySubset",
    signature = c(x = "myclass", subset = "character"),
    definition = function(x, subset){ 
        # different function body 
    }
)

## default method using "ANY" (lower priority)
setMethod(
    f = "mySubset",
    signature = c(x = "myclass", subset = "ANY"),
    definition = function(x, subset){ 
       ## insert default behaviour (might be an error),
       ## a call to subset.default or whatever
    }
)

## now write an S3 method for subset that calls the S4 generic if
## x is of class myclass

subset.myClass <- function(x,subset){
  mySubset(x,subset)
}

This preserves the S3-only behaviour of subset, but you now have S4 level control over method dispatch provided that x is of class myclass.

Your users don't need to appreciate this distinction; they can still call subset(x,class) in the same way they are accustomed to, when x has your new class.

JDL
  • 1,496
  • 10
  • 18