Dynamically Add Function to R6 Class Instance

dynamically add function to r6 class instance

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.

Using set to dynamically construct R6 class and instance

The following solution entails dynamically creating a string form of the entire function/method definition, and then calling eval(parse()) on it.

library("R6")

df <- tibble::tribble(~var, ~val,
"min_units", 100,
"min_discount", 999)

create_thresholds <- function(df) {
Thresholds <- R6Class("Thresholds")

for (i in 1:nrow(df)){
mthd_name <- df$var[i]
mthd_def <- glue::glue("function() private$.{mthd_name}")
Thresholds$set("private", glue(".{mthd_name}"), df$val[i])
Thresholds$set("active", mthd_name, eval(parse(text = mthd_def)))
}

hh <- Thresholds$new()
return(hh)
}

hh <- create_thresholds(df)
hh$min_discount # expect 999!
hh$min_units #expect 100

How to add functions in a loop to R6Class in R

I've asked on GitHub after adding this question and they given the answer very quickly. Here is re-post of the answer:

get <- function(x = list()) {
class <- R6::R6Class(classname = "class")
lapply(names(x), function(name) {
fn <- eval(substitute(function() subst_name, list(subst_name = name)))
class$set("public", name, fn)
})
class
}
x <- get(x = list(a = 10, b = 20))$new()
x$a()

and if you want better name when you print x$a you can clear name ref using:

attr(fn, "srcref") <- NULL

EDIT:

and here is example when values added to class are functions (this is my improved code):

constructor <- function(public = list(), private = list()) {
class <- R6::R6Class(classname = "class")
lapply(names(public), function(name) {
if (is.function(public[[name]])) {
env <- environment(public[[name]])
env$self <- public
env$private <- private
fn <- eval(substitute(function(...) fn(...), list(fn = public[[name]])))
class$set("public", name, fn)
} else {
class$set("public", name, public[[name]])
}
})
class
}
test <- function() {
a <- 10
class <- constructor(
public = list(
a = function() { a + self$b },
b = 20
)
)
x <- class$new()
x$a()
}

test()

and if you want to have access to super you need to use this code:

EDIT2:

component <- function(public = NULL,
private = NULL,
static = NULL,
...) {
class <- R6::R6Class(...)
r6.class.add(class, public)
r6.class.add(class, private)
class$extend <- make.extend(class)
class
}

#' helper function for adding properties to R6Class
r6.class.add <- function(class, seq) {
prop.name <- as.character(substitute(seq)) # so we don't need to write name as string
lapply(names(seq), function(name) {
if (is.function(seq[[name]])) {
## the only way to have scope from when function was create with self and private
## eval substitute simply circument R6 encapsulation and use scope from where function
## was created (closure) and env.fn patch the env of inner function so it get self
## and private as magic names - this is done so component function the same as
## when R6Class is created inline - so component is referencial transparent and can
## be replaced with R6Class
fn <- eval(substitute(function(...) {
## patch function env
fn <- fn.expr # fn.expr will be inline function expression
parent <- parent.env(environment())
## we don't overwrite function scope so you can nest one constructor
## in another constructor
env <- new.env(parent = environment(fn))
env$self <- parent$self
env$super <- parent$super
env$private <- parent$private
environment(fn) <- env
fn(...)
}, list(fn.expr = seq[[name]], name = name)))
class$set(prop.name, name, fn)
} else {
class$set(prop.name, name, seq[[name]])
}
})
}

instead of env$self <- parent$self you can also use get("self", parent) (it will search the environment chain for the variable).

Initialize R6 class with an instance of Class and return the same Class

Under current state of R6 this seems to not be possible. Check the raw code github.com/r-lib/R6/blob/master/R/new.R . Most important are lines 154 where is initialization applied and 194 where public_bind_env is returned . The problem is that even with super assignment I think we could not overwrite it as all things are built here from a new empty env with own address.
This solution which using a wrapper is widthly used in the market and it is doing what it should:

class1 <- function(x = NULL) {

if(inherits(x, "Class1")) x else Class1$new(x)

}

Class1 = R6::R6Class("Class1",
public=list(
initialize = function(x = NULL){
}
))
# Initiate an instance from scratch
foo = class1()
# Initiate an instance through another instance
bar = class1(foo)
# The two should be the same
identical(foo, bar)
#> TRUE

Access elements of an R6 class that instantiates another R6 class

Extend SimpleClass2’s constructor to take an object of type SimpleClass, and pass self when calling the constructor in SimpleClass::cls_two:

SimpleClass2 <- R6::R6Class(
"SimpleClass2",
public = list(
initialize = function(obj) {
private$b <- 2
private$obj <- obj
},
get_a = function() private$obj
),
private = list(
b = numeric(),
obj = NULL
)
)

R - R6 - higher order function - scope of enclosing function

I rewrote my solution to this:

init_func_generator = function(cls, spec) {
if(length(spec) == 0) return(function() { })

args <- alist()
for(s in spec) {
args[[s[[1]]]] <- s[[2]]
}

func <- function() {
argg <- as.list(environment())
for(arg_name in names(argg)) {
self[[arg_name]] <- argg[[arg_name]]
}
}
formals(func) <- args

return(func)
}

So now I can do:

  print(s$Set$new(a_set=1)$a_set)
[1] 1

print(s$Set$new()$a_set)
[1] NA

when the spec in this case is [('a_set', NA)]



Related Topics



Leave a reply



Submit