Search code examples
roopr-s4

S4 class constructor and validation


I present a short code to create a S4 class myclass and ensure that objects are created if they verify a condition given by a parameter param

setClass("myclass", slot = c(x = "numeric"))

 #constructor
 ValidmyClass<- function(object, param = 1)
{
 if(object@x  == param) return(TRUE)
 else return("problem")

}
setValidity("myclass", ValidmyClass)

setMethod("initialize","myclass", function(.Object,...){
.Object <- callNextMethod()
validObject(.Object,...)
.Object
})

For which I get the following error message Error in substituteFunctionArgs(validity, "object", functionName = sprintf("validity method for class '%s'", : trying to change the argument list of for validity method for class 'myclass' with 2 arguments to have arguments (object)

I understand the issue with the arguments but I cannot find a way to solve this. The document about setValidity mentions that the argument method should be "validity method; that is, either NULL or a function of one argument (object)". Hence from my understanding excluding more than one argument.

Nevertheless, the idea behind this example is that I want to be able to test the construction of a myclass object based on the value of an external given parameter. If more conditions were to be added, I would like enough flexibility so only the function ValidmyClass needs to be updated, without necessarily adding more slots.


Solution

  • The validity function has to have one argument named object. When I need to create one argument functions but really have more arguments or data to pass in I often fall back to using closures. Here the implementation of your ValidmyClass changes in that it will now return the actual validity function. The argument of the enclosing function is then the set of additional arguments you are interested in.

    setClass("myclass", slot = c(x = "numeric"))
    
    #constructor
    ValidmyClass <- function(param) {
      force(param)
      function(object) {
        if (object@x  == param) TRUE
        else "problem"
      }
    }
    
    setValidity("myclass", ValidmyClass(1))
    

    Also the validity function is called automatically on init; however not when the slot x is changed after the object is created.

    setMethod("initialize", "myclass", function(.Object,...) {
      .Object <- callNextMethod()
      .Object
    })
    
    new("myclass", x = 2)
    new("myclass", x = 1)
    

    For more information on closures see adv-R. Although I think this answers your question, I do not see how this implementation is actually helpful. When you define your class, you basically also fix the additional parameters which the validity function knows about. If you have several classes for which you can abstract the validity function then I would use the closure. If you have one class with changing parameters at runtime, I would consider to add a slot to the class. If you do not want to alter the class definition you can add a slot of class list where you the can pass in an arbitrary number of values to test against.