Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dynamically add function to r6 class instance

Tags:

r

r6

I'm trying to forget refclasses (R5) and move to R6 but there is a problem with dynamic code. I would add a new function and it works in R5:

clsTrn <- setRefClass("clsTrn",
  fields = list(x = "numeric"),
  methods = list(
    add_function = function(rcode) {
      eval(parse(text=rcode), envir=.self)
    }
  )
)  

cls <- clsTrn$new(x=4)
cls$x
# [1] 4
cls$add_function("predict = function(y) {return(.self$x*y)}")

cls$predict(3) 
#[1] 12

Similar code doesn't work for R6.

library(R6)

clsTrnR6 <- R6Class("clsTrnR6",
  lock=FALSE,
  public = list(
    x = NA,
    initialize = function(x) {
      self$x <- x
    },
    add_function = function(rcode) {
      eval(parse(text=rcode), envir=self)
    }
  )
)  


clsR6 <- clsTrnR6$new(x=4)
clsR6$x
#[1] 4

clsR6$add_function("predict = function(y) {return(self$x*y)}")
# Błąd weval(expr, envir, enclos) : nie udało się znaleźć funkcji '='
clsR6$predict(3)

Adding predict in class definition changes nothing, the same error. Is there any solution? Thanks in advance.

> sessionInfo()
R version 3.1.1 (2014-07-10)
Platform: x86_64-pc-linux-gnu (64-bit)

locale:
 [1] LC_CTYPE=pl_PL.UTF-8       LC_NUMERIC=C               LC_TIME=pl_PL.UTF-8        LC_COLLATE=pl_PL.UTF-8     LC_MONETARY=pl_PL.UTF-8   
 [6] LC_MESSAGES=pl_PL.UTF-8    LC_PAPER=pl_PL.UTF-8       LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=pl_PL.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] R6_2.0

loaded via a namespace (and not attached):
[1] codetools_0.2-8 rpart_4.1-5     tools_3.1.1    
> 

Added: After great @G.Grothendieck answer, I have string based function definition, but maybe there is more elegant solution.

library(R6)

clsTrnR6 <- R6Class("clsTrnR6",
  lock=FALSE,
  public = list(
    x = NA,
    initialize = function(x) {
      self$x <- x
    },
    add_function = function(name, meth) {
      self[[name]] <- meth
      environment(self[[name]]) <- environment(self$add_function)
    },
    add_function2 = function(name, meth) {
      eval(parse(text=paste0("predict <- ",meth)))
      self[[name]] <- predict
      environment(self[[name]]) <- environment(self$add_function)
    }
  )
)  

clsR6 <- clsTrnR6$new(x=4)
clsR6$x

#[1] 4

clsR6$add_function2("predict", "function(y) y*self$x")
clsR6$predict(11)

#[1] 44
like image 855
Daniel Avatar asked Oct 12 '14 23:10

Daniel


3 Answers

Try this. Like the reference class example it adds a function to the object (not the class). Here name is a character string containing the name of the function/method and meth is the function/method itself:

clsTrnR6 <- R6Class("clsTrnR6",
  lock=FALSE,
  public = list(
    x = NA,
    initialize = function(x) {
      self$x <- x
    },
    add_function = function(name, meth) {
      self[[name]] <- meth
      environment(self[[name]]) <- environment(self$add_function)
    }
  )
)  
clsR6 <- clsTrnR6$new(x=4)
clsR6$x
#[1] 4
clsR6$add_function("predict", function(y) y*self$x)
clsR6$predict(11)
## 44

Added Note that this is also easy to do using proto. It does not require a special add_function. We will use an upper case P to denote the proto object that plays the role of a class (called a "Trait" in the proto vignette) and use lower case p to denote the proto object that plays the role of an instance:

library(proto)

P <- proto(new = function(., x) proto(x = x))
p <- P$new(x = 4)

p$predict <- function(., y) .$x * y
p$predict(11)
## 44

Although its common to use . to refer to the object in proto you can use the name self (or any name you like) in place of . if you prefer.

like image 171
G. Grothendieck Avatar answered Nov 07 '22 12:11

G. Grothendieck


You can use $set() method on the generator object. So you will change the class definition not the object.

clsTrnR6$set("public", "predict", function(y) self$x*y)
clsR6 <- clsTrnR6$new(x=4)
clsR6$predict(3)
[1] 12

Edit:

Changing the class definition means that the object created prior to using the $set modifier will not have the predict function.

like image 5
agstudy Avatar answered Nov 07 '22 10:11

agstudy


There is a simple workaround for this problem. Set the default value of the class method to NULL and update this value within the initialize() method. Now, you can change the method as you like without receiving this error. For example:

aClass <- R6::R6Class("className",
  public = list(
    f = NULL,
    initialize = function(...) {
      self$f = sum
    },
    update_f = function(x) {
      self$f = x
    }
  )
)

test <- aClass$new()
test$f
test$update_f(mean)
test$f

Or, one can modify the function in-place:

test$f <- median
test$f

That should resolve the issue. I also posted this answer here.

like image 2
Adam Erickson Avatar answered Nov 07 '22 11:11

Adam Erickson