How to tell an R reference class object to update

2019-06-01 18:49发布

问题:

I'm looking for a way to tell an instance of a reference class to forget one of its method definitions. For example, I create the class MyReferenceClass and an instance called my_object I can call the method print_hello and everything works:

MyReferenceClass <- setRefClass("MyReferenceClass",
    methods = list(
        print_hello = function(){
            print("hello")
        }
    )
)
my_object <- MyReferenceClass$new()
my_object$print_hello() # "hello"

If I update the class definition by adding a new method (print_goodbye) my existing object will be able to use it. But if I change a previously defined method (print_hello), it won't update:

MyReferenceClass <- setRefClass("MyReferenceClass",
    methods = list(
        print_hello = function(){
            print("hello_again")
        },
        print_goodbye = function(){
            print("goodbye")
        }
    )
)
my_object$print_goodbye() # "goodbye" => it works
my_object$print_hello() # "hello" => it doesn't work

Is there a way to tell my_object to forget about its definition of print_hello? This doesn't work: my_object$print_hello <<- NULL

回答1:

AFAIK the answer is no when trying to inform the object about the class def change "after the fact", i.e. after it has been instantiated/created.

Once you created an instance of a S4 Class, that object is "bound" to the class def as it was when you created the object. And in my opinion this makes perfect sense. Not sure if the "successful" update for formerly missing methods (i.e. print_goodbye()) simply works "by accident" or actually is the desired behavior.

Recommended way to deal with updated class defs

My recommendation: if you decide you want/need to update your class defs, you're just safer off by re-sourcing your entire project code. That way you make sure everything is in place before you create actual instances. I'd consider anything else to be quite a hack that stands on very shaky grounds.

If you decide to hack anyway

There might be some dirty way to hack the hidden .refClassDef object field of an Reference Class instance that actually contains the class def (see my_object$.refClassDef). But setting this field (i.e. using <- on it) didn't work:

my_object$.refClassDef <- MyReferenceClass
Error in envRefSetField(x, what, refObjectClass(x), selfEnv, value) : 
  '.refClassDef' is not a field in class "MyReferenceClass"

Neither did an explicit assignment via assign():

assign(".refClassDef", MyReferenceClass, my_object)
Error in assign(".refClassDef", MyReferenceClass, my_object) : 
  cannot change value of locked binding for '.refClassDef'

An even deeper hack would probably involve looking at attributes(my_object$.refClassDef). There you might find the actual pieces that make up the ref class def. However, I don't know if even changing anything there would be "immediately" reflected.

Also, resetClass() might give you some more insights.


UPDATE: 2014-03-19

For handling your caching-approach two approaches come to mind:

1. The most evident way: use copy()

See ?setRefClass

MyReferenceClass <- setRefClass("MyReferenceClass",
    methods = list(
        print_hello = function(){
            print("hello")
        }
    )
)
my_object <- MyReferenceClass$new()
MyReferenceClass <- setRefClass("MyReferenceClass",
    methods = list(
        print_hello = function(){
            print("hello_again")
        },
        print_goodbye = function(){
            print("goodbye")
        }
    )
)

Before copying:

my_object$print_hello()
[1] "hello"

After copying:

my_object <- my_object$copy()
my_object$print_hello()
[1] "hello_again"

2. Hacking at attributes(my_object$.refClassDef)$refMethods (OUTLINE, NOT WORKING YET)

Even though I wouldn't recommend actually relying on something like this, hacks are always a great way to get a deeper understanding of how things work.

In this case, we could try to modify attributes(my_object$.refClassDef)$refMethods which is an environment that contains the actual method defs as I'm guessing that this is where the object "looks" when a method is called.

It's no problem overwriting the actual method defs, yet it seems to have no immediate effect. I'm guessing that there are more "links" to the "old" class def involved that would need to be updated manually in a similar way.

Note that my_object still features the method print_hello that prints "hello":

attributes(my_object$.refClassDef)$refMethods$print_hello
Class method definition for method print_hello()
function () 
{
    print("hello")
}

This is how an overwriting function might look like:

ensureRecentMethods <- function(obj, classname) {
    ## Get generator //
    gen <- getRefClass(classname)

    ## Get names of methods belonging to the class of 'obj' //
    ## This will serve as an index for the update
    idx1 <- names(Filter(function(x) {attr(x, "refClassName") == class(obj)}, 
        as.list(attributes(obj$.refClassDef)$refMethods))
    )
    #idx2 <- names(Filter(function(x) {attr(x, "refClassName")==gen$className}, 
    #    as.list(gen$def@refMethods)
    #))
    ## Note:
    ## 'idx2' could be used to enforce some validity checks such as 
    ## "all old methods must also be present in the updated class def"

    ## Overwrite //
    for (ii in idx1) {
        ## Note how we are overwriting the old method defs in environment
        ## 'attributes(obj$.refClassDef)$refMethods' with the updated 
        ## definitions taken from the generator of the updated class 
        ## 'gen$def@refMethods[[ii]]' by making use of the index retrieved 
        ## one step before ('idx1')
        expr <- substitute(
            assign(x=X, value=VALUE, envir=ENVIR),
            list(
                X=ii,
                VALUE=gen$def@refMethods[[ii]],
                ENVIR=attributes(obj$.refClassDef)$refMethods
            )
        )
        eval(expr)
    }

    ## As at the end of the day ref class objects are nothing more than 
    ## environments, there is no need to explicitly return the actual 
    ## ref class object 'obj' as the original object has already 
    ## been updated (pass-by-reference vs. pass-by-value)
    return(TRUE)
}

Applying it:

ensureRecentMethods(obj=my_object, classname="MyReferenceClass")

Even though the def of print_hello was indeed overwritten, the object still grabs the "old" version somehow:

attributes(my_object$.refClassDef)$refMethods$print_hello 
## Note the updated method def!

Class method definition for method print_hello()
function () 
{
    print("hello_again")
}

my_object$print_hello()
[1] "hello"


回答2:

Take advantage of my_class@generator$def@refMethods

How about including an update method in the original class, as did here,

Manual modifications of the class definition of a Reference Class instance