Non-standard set-functions in R Reference Classes
Asked Answered
A

2

10

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.

Avilla answered 15/1, 2019 at 20:56 Comment(7)
I would think the "particular name" for such functions would be "assignment". You should also realize that $<- is a synonym for [[<-, although you code suggests you might have already understood that.Aurora
@42- Yeah, but assignment would usually refer to the assign-function or <- which of course also is a function. As for $<- and [[<-, I'd say these are examples of 'assignment functions' in a subsetting context. I was more asking for a name for all 'foo<-'(x, value) functions which admits to the foo(x) <- value syntax. So I would looking for a more specific name (if it exists) for functions of this type if you will. In other words, how do I google it?Avilla
I think searching the documentation files would be more likely to return a manageable number of hits.Aurora
@42- Thanks. I tried once again (following the docs for [[<- as you suggested), and this led me here and here. So the names seems to be 'replacement functions' or 'subset assigment'.Avilla
So "named assignment" or "assignment by character index" or perhaps "sub-assignment" might trigger the appropriate set of neurons in someone who's more conversant with Reference class functions on Rhelp or R-devel than am I.Aurora
I guess that this has little to do with reference classes and it doesn't work for the same reason names(1:10)<-letters[1:10] doesn't work. The assignment functions work if the first argument is already assigned and it's the object that gets modified/replaced.Konstanz
@Nicola Thank you for the comment. I see what you mean, but I cannot help for feel this is not quite the same because the first argument refers to a existing object that is mallable (while not an assigned 'object' itself I know). But I guess you are right as 'names<-'(1:10, letters[1:10]) works just fine (like in the case above).Avilla
B
3

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.

Benford answered 11/2, 2019 at 17:14 Comment(2)
Thanks for the answer. Although I still don't understand why it does not work, I'm starting to accept it indeed is impossible. I definitely like your suggestion for the alternative syntax. But maybe I'll just have live with a standard reference class assignment function---I'm not sure I want to bring the S3 system into this and add another layer of complexity. This is using the S3 system, right? In any case, if no other answers comes closer to my wanted syntax I'll award you the answer.Avilla
Correct, this is ultimately using S3. Just note that the setGeneric and setMethod will typically pop up in the context of S4 first.Benford
M
3

I am probably misunderstanding what you are trying to achieve but what's wrong with this?

person = setRefClass("Person",
                     fields = list(
                       fullname = "list",
                       gender = "character"
                     ))

a_person = person$new(fullname = list(first = "James", last = "Brown"), gender="M")

a_person$fullname$first = "Bob"

a_person

Reference class object of class "Person"
Field "fullname":
$`first`
[1] "Bob"

$last
[1] "Brown"

Field "gender":
[1] "M"
Murcia answered 8/2, 2019 at 13:18 Comment(1)
Well, there nothing wrong with it in itself. But the project I'm working prefer get and set functions and not manipulating fields directly. Secondly, the use case is not as simple as the one above. The fields are more complicated data structures where something 'special' needs to be done. Lastly, I just find assignment-functions (myfunction(x) <- value) more idiomatic to R than the regular set-functions in RCs (myfunction(x, value)).Avilla
B
3

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.

Benford answered 11/2, 2019 at 17:14 Comment(2)
Thanks for the answer. Although I still don't understand why it does not work, I'm starting to accept it indeed is impossible. I definitely like your suggestion for the alternative syntax. But maybe I'll just have live with a standard reference class assignment function---I'm not sure I want to bring the S3 system into this and add another layer of complexity. This is using the S3 system, right? In any case, if no other answers comes closer to my wanted syntax I'll award you the answer.Avilla
Correct, this is ultimately using S3. Just note that the setGeneric and setMethod will typically pop up in the context of S4 first.Benford

© 2022 - 2024 — McMap. All rights reserved.