Daniel
Daniel

Reputation: 133

dynamically add function to r6 class instance

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

Upvotes: 8

Views: 2142

Answers (3)

Adam Erickson
Adam Erickson

Reputation: 6363

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.

Upvotes: 3

G. Grothendieck
G. Grothendieck

Reputation: 269694

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.

Upvotes: 7

agstudy
agstudy

Reputation: 121578

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.

Upvotes: 5

Related Questions