Dynamically add a function to an instance of class r6
I'm trying to forget refclasses (R5) and move to R6, but there is a dynamic code problem. I would add a new feature 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 a prediction in the class definition doesn't change anything, 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: Following the answer by the great @ G. Grothendieck, I have a string based function definition, but maybe there is a 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
source to share
Try it. Like the reference class example, it adds a function to the object (not to 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 with proto. It doesn't require a special one add_function
. We will use uppercase P
to denote a proto-object that plays the role of a class (called a "feature" in a proto-vignette) and use lowercase P
to denote a proto-object that plays the role of an example:
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 this is commonly used to refer to an object in proto .
, you can use a name self
(or any other name) instead .
if you like.
source to share
You can use $set() method
for a generator object. This way you 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 an object created before using the $ set modifier will not have a function predict
.
source to share