Reference Classes, tab completion and forced method definition
Asked Answered
V

3

7

I am currently writing a package using reference classes. I have come across an issue which from reading various sources:

Method initialisation in R reference classes

Can't reliably use RefClass methods in Snowfall

I gather is caused because reference methods are not all copied to every object in the class rather they are copied when first accessed.

https://stat.ethz.ch/pipermail/r-devel/2011-June/061261.html

As an example define:

test <- setRefClass("TEST",
                fields = list( a = "numeric"),
                methods = list(
                   addone = function(){
                                        a <<- a+1
                                      },
                   initialize = function(){
                                            a <<- 1
                                          }
                              )
               )

example <- test$new()

So example is a new object of class TEST. Typing example$ and tabbing in the console gives

> example$
# example$.->a         example$.refClassDef example$.self        
# example$a            example$initialize 

so the method addone is not presented as an option. It is available to call however:

example$addone()

Now tabbing again reveals

# > 
# > example
# Reference class object of class "TEST"
# Field "a":
# [1] 2
# > example$
# example$.->a         example$.refClassDef example$.self        
# example$a            example$addone       example$field        
# example$initialize   example$show

so now addone and field and show are presented as options.

Martin Morgan advises to force definition of the methods in one of the above links. This works well

test <- setRefClass("TEST",
                fields = list( a = "numeric"),
                methods = list(
                   addone = function(){
                                        a <<- a+1
                                      },
                   initialize = function(){
                                            a <<- 1
                                            .self$addone #force definition
                                          }
                              )
               )

example <- test$new()

so now tabbing gives:

# > example$
# example$.->a         example$.refClassDef example$.self        
# example$a            example$addone       example$initialize  

Some of my classes have over 30 methods so I would like to do this as succintly as possible. I have defined:

test <- setRefClass("TEST",
                fields = list( a = "numeric"),
                methods = list(
                   addone = function(){
                                        a <<- a+1
                                      },
                   initialize = function(){
                      a <<- 1
                      eval(parse(text=paste0('.self$',ls(test$def@refMethods))))
                                          }
                              )
               )

example <- test$new()

tabbing now gives:

# > example$
# example$.->a         example$.refClassDef example$.self        
# example$a            example$addone       example$callSuper    
# example$copy         example$export       example$field        
# example$getClass     example$getRefClass  example$import       
# example$initFields   example$initialize   example$show         
# example$trace        example$untrace     

Whilst this works it feels a bit clumsy. Also test$def@refMethods is used rather then getRefClass("TEST")$def@refMethods so that feels a bit wrong. Has anyone dealt with this issue before.

Is there a better way to approach a solution? Thanks for any advice and apologies if the question is overly drawn out.

Vestavestal answered 22/9, 2012 at 11:1 Comment(0)
P
6

I wonder what your objective is? Function names showing up with tab completion? Then it's worth a post to the R-devel mailing list with a feature request. The original scenario is more elegantly handled with usingMethods as documented on ?setRefClass. A continued hack might be

initialize = function(...) {
    methods <- getRefClass(class(.self))$methods()
    eval(parse(text=paste0(".self$", methods)))
    callSuper(...)
}

Tab completions can be customized via .DollarNames in the utils package, so

.DollarNames.TEST <- function(x, pattern)
    grep(pattern, getRefClass(class(x))$methods(), value=TRUE)

Maybe an S3 method could be written at the base of your class hierarchy for this?

Paramagnetism answered 22/9, 2012 at 15:59 Comment(4)
Thank you that is very helpful and more elegant than my solution. Also I didnt know it was termed tab completion. Knowing this term enabled me to find the relevant package in utils. Tab completion with reference classes was my objective and I will edit the question title to better reflect this.Vestavestal
This seems very promising. Would it be a bad idea to add .DollarNames.envRefClass <- function(x, pattern) grep(pattern, getRefClass(class(x))$methods(), value=TRUE) to my package?Vestavestal
Yes, it would be a bad idea -- you'd change behavior for all reference classes, not just your own. Perhaps some like it's current behavior, perhaps methods will at some future date implement a super-duper .DollarNames.envRefClass and your long-forgotten method will then override this. The right thing to do is to bring this up on R-devel as a feature request, especially now that you have at least a working prototype to illustrate the behavior you think appropriate.Paramagnetism
Martin Morgan thank you for your time and expertise. I will post a feature request to R-devel.Vestavestal
T
3

I know this is an old question but it is still the top entry when searching for refClass tab completion on google, so I'll just add an update:

Instead of using grep in the .DollarNames function as suggested by Martin, use findMatches from the utils package as it plays better with the different Rgui's around (grep will delete your partially typed name upon hitting tab)

.DollarNames.TEST <- function(x, pattern){
    utils:::findMatches(pattern, getRefClass(class(x))$methods())
}

This is also how tab completion is handled internally for lists and data.frames

Thad answered 3/11, 2014 at 9:50 Comment(0)
V
1

@Martin Morgan noted that this was termed tab completion. The package rcompletion and later rcompgen were tasked with achieving this. They have been now moved to utils.

rcompletion update

I looked thru the code for completion.R and from what I could determine utils:::.DollarNames.environment was handling tab completion for reference classes.

completion.R

Redefining the function seemed to achieve tab completion:

assignInNamespace( x = ".DollarNames.environment",
                     function(x, pattern = "") {
    y <- NULL
    if(isS4(x) && !is.null(x[['.refClassDef']])){
      if(.hasSlot(x$.refClassDef,'refMethods')){
        y<-x$.refClassDef@refMethods
        y<-ls(y, all.names = TRUE, pattern = pattern)
      }
    }
    x<-ls(x, all.names = TRUE, pattern = pattern)
    unique(c(x,y))
                                               }
,ns = "utils")

Some things to note:

  • I would only use this for my own use. Currently I am debugging and documenting a package. I had some longish method names and couldnt remember exactly what they were so tab completion will help greatly.

  • Usage of assignInNamespace in a package is frowned upon (if not banned) see ?assignInNamespace.

  • Forced definition of methods is more advisable.

Vestavestal answered 23/9, 2012 at 1:51 Comment(1)
Actually, .DollarNames is an S3 generic so you can write S3 methods on it; I illustrated this in my answer.Paramagnetism

© 2022 - 2024 — McMap. All rights reserved.