dynamically add function to r6 class instance

亡梦爱人 提交于 2019-11-27 02:24:37

问题


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

回答1:


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.




回答2:


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.




回答3:


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.



来源:https://stackoverflow.com/questions/26331030/dynamically-add-function-to-r6-class-instance

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!