Search code examples
rreference-class

Non-standard set-functions in R Reference Classes


Is it possible to get the syntax

foo$bar(x) <- value

to work where foo is a reference class object and bar is a method? I.e. is it possible to do "subset assigment" and have "replacement functions" as methods in Reference Classes?

Is the syntax possible to get with other OO systems?

Example: I'll illustrate with a made-up use case. Imagine a reference class, Person, which contains some basic information of a person. Particularly, one field called fullname is a named list:

PersonRCGen <- setRefClass("Person",
                           fields = list(
                             fullname = "list",
                             gender = "character"
                           ))

Next, we should define some methods to get and set particular names within the fullnames list which (try) to give the above syntax/interface. My best attempt has so far been:

PersonRCGen$methods(
  name = function(x) { # x is the dataset,
    .self$fullname[[x]]
  },
  `name<-` = function(x, value) {
    .self$fullname[[x]] <- value
  }
)

The naming here should also illustrate what I'm trying to do.

We initialize a new object:

a_person <- PersonRCGen$new(fullname = list(first = "Jane", last = "Doe"),
                            gender = "F")

Accessing the fullname field directly and accessing the first and last name by the defined get-function works as intended:

a_person$fullname
#$`first`
#[1] "Jane"
# 
#$last
#[1] "Doe"

a_person$name("first")
#[1] "Jane"

a_person$name("last")
#[1] "Doe"

However, for setting a particular name in the fullname list, I'd like to have the following syntax/interface which unfortuantely fails.

a_person$name("first") <- "Jessie"
#Error in a_person$name("first") <- "Jessie" : 
#  target of assignment expands to non-language object

I know the following works (which basically renders the method poorly named).

a_person$`name<-`("first", "Johnny")
a_person$fullname
#$`first`
#[1] "Johnny"
#
#$last
#[1] "Doe"

In my real use case, I'd like to avoid 'traditional' getName(x) and setName(x, value) names for the get and set functions.


Solution

  • I don't think you can do this with your desired syntax.

    Note that you will get the same error if you run any assignment like that, e.g.

    a_person$hello("first") <- "John"
    

    so it's really a basic problem.

    What does work, is the following syntax:

    name(a_person, "first") <- "John"
    

    Altogether you could then have something like below:

    PersonRCGen <- setRefClass("Person",
                      fields = list(
                        fullname = "list",
                        gender = "character"
                      ),
                      methods = list(
                        initialize = function(...) {
                          initFields(...)
                        },
                        name = function(x) {
                          .self$fullname[[x]]
                        }
                      )
    )
    
    setGeneric("name<-", function(x, y, value) standardGeneric("name<-"))
    setMethod("name<-", sig = "ANY", function(x, y, value) {
      UseMethod("name<-")
    })
    # some extras
    "name<-.default" <- function(x, y, value) {
      stop(paste("name assignment (name<-) method not defined for class", class(x)))
    }
    "name<-.list" <- function(x, y, value) {
      x[[y]] <- value
      return(x)
    }
    # and here specifically
    "name<-.Person" <- function(x, y, value) {
      x$fullname[[y]] <- value
      return(x)
    }
    
    # example to make use of the above
    a_person <- PersonRCGen$new(
      fullname = list(
        first = "Jane",
        last = "Doe"
      ),
      gender = "F"
    )
    
    a_person$name("first")
    #> [1] "Jane"
    name(a_person, "middle") <- "X."
    a_person$name("middle")
    #> [1] "X."
    

    I'm aware this is not exactly what you want but I hope it helps.