Search code examples
rr-s4

What is the recommended pattern to design a function that can implement multiple algorithms for S4 class in R?


I have an object of class S4 "MyOb" and a generic function "MyFun". I would like to implement multiple different algorithms for MyFun to process MyOb and be able to select the algorithm I want by specifying the "type" in the generic function. Type would be an argument of MyFun and would be just a character (string): "Algo1", "Algo2"...

However, each algorithm would require different arguments. I have started has indicated in the code below but then I am not sure how to continue, should I have a switch in the setMethod function that redirect to other separate functions ?

setGeneric("MyFun", function(x, type, ...) standardGeneric("MyFun"))

setMethod("MyFun", c("MyOb", "character"), function(x, type, ...){

       switch()??? #to Algo1, Algo2, ....
})

Algo1<-function(x, M, N){ #blabla }
Algo2<-function(x, F, G, H){ #blablabla }

Ideally, I like to end up with something like the function baseline in the baseline R package, with MyFun.Algo1, MyFun.Algo2 being the different function and MyFun the generic one...

I have been looking for this type of pattern but could not find any tutorial...

Any hint, advice, recommendation would be appreciated!

Thank you very much!


Solution

  • Firstly, you probably want to only have x as the signature of your function (you don't want a different method based on the class of type, for example). So you should start with

    setGeneric("MyFun", function(x, type, ...) standardGeneric("MyFun"), signature="x")
    

    (you don't even have to have type amongst the arguments to the generic if you don't want to — it depends whether it would be used for other classes of input.)

    If you need different other arguments for later algorithms, that is fine. The ... sorts this out for you. So if you have your two algorithms

    Algo1<-function(x, M, N){ #blabla }
    Algo2<-function(x, F, G, H){ #blablabla }
    

    then these will get called correctly when you call

    MyFun(x,type="Algo1",M=1,N=2) ## dispatched Algo1 with M=1 and N=2
    MyFun(x,type="Algo2",F=3,G=4,H=-2.7) ## dispatches Algo2 with F, G and H
    

    The recommended way to write the MyFun method is as follows (you were right with your intuition to use switch):

    setMethod("MyFun",signature(x="MyObj"), function(x,type=c("Algo1","Algo2"),...){
      type <- match.arg(type)
      switch(type,
        Algo1=Algo1(x,...),
        Algo2=Algo2(x,...),
        stop("unknown algorithm")
      )
    })
    

    It would probably be wise to make sure that Algo1 and Algo2 do some argument checking to make sure they are receiving the arguments they expect. This is good programming practice in general, but perhaps more important here.

    If you haven't come across match.arg before, it's the recommended way of ensuring an argument matches one of a defined set of values. It uses the default argument as the list of allowed values.