Search code examples
rr-s4

What is distinction between initialize method vs prototype in setClass to set default values in R S4?


In R S4 OOP, I'm aware of 2 ways so set default values for a particular class.

1.) Using prototype in setClass()

setClass("Person",
         slots = c(
           name = "character",
           age = "numeric"
         ), 
         prototype = list(
           name = "Jim",
           age = 25
         )
         )

2.) Using initialize in setMethod()

setMethod("initialize", "Person",
          function(.Object,
                   name,
                   age,
                   ...) {
              .Object@name <- "Jim"
              .Object@age <- 25
            validObject(.Object)
            return(.Object)
          }
)

assign("Person", Person, envir = .GlobalEnv)

Can anyone please elaborate on the distinction between these two i.e. why would we want to include a separate initialize method vs using prototype() in setClass()? What is S4 best practice?

Thank you in advance for the response.


Solution

  • The prototype is only able to handle simple cases; in particular the value of slots cannot depend on each other and the user cannot specify any dynamic values. For example, if you have a simple class

    setClass("personInfo",
      slots=list(
        age="numeric",
        ageSquared="numeric"
      ), prototype=prototype(
        age=18,
        ageSquared=18^2
    ) -> personInfo
    

    then the prototype can only create 18-year old people. Instead, it is more useful to create an initialize method as a function of age, which can then set both age and ageSquared to the relevant values.

    Also note that if you assign the results of setClass into an object (as I did in the example above, assigning it into an object called personInfo then personInfo becomes a call to initialize and you can now call e.g.

    personInfo(age=10,ageSquared=100)
    

    (though this isn't ideal in this case as you have to explicitly supply ageSquared when you could have had the method do that for you.)

    I would recommend:

    • using a prototype for any slot that has a reasonable default value (every class/slot has an implicit prototype anyway)
    • always keeping the return value from setClass as an object
    • over-riding that with a method of initialize if some slots depend on others (like ageSquared here)