Reputation: 66560
I want to write wrapper over R6Class but it don't work, I've tried multiple things after I've found this question dynamically add function to r6 class instance
So I've tried this, they both don't work:
get <- function(x = list()) {
class <- R6::R6Class(classname = "class")
for (name in names(x)) {
class$set("public", name, function() name)
}
class
}
x <- get(x = list(a = 10, b = 20))$new()
x$a()
# b
x$b()
# b
this is because of the looping with closures the for don't create new scope. So I've tried this:
get <- function(x = list()) {
class <- R6::R6Class(classname = "class")
lapply(names(x), function(name) {
print(name)
class$set("public", name, function() name)
})
class
}
x <- get(x = list(a = 10, b = 10))$new()
x$a()
this will throw error that name is not defined, because this behavior of R6Class that everything is is in eval substitute
, so there is no way to make new function that take scope/environment from where it was called. Or is there a way?
My real issue is that I want to create function wrapper and I want to call:
fn <- function() {
x <- 10
y <- myFunction(public = list(
foo = function(y) {
x + y
}
})
z <- y$new()
z$foo(10)
## I want 20 as result
}
Is there a way to create myFunction
function that will create R6Class? The reason why I want this is because I have component system based on R6Class and want to remove some boilerplate that need to be added to each class so it's easier to use. I don't want to create new class system, I would like to use R6 classes.
Upvotes: 2
Views: 320
Reputation: 66560
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).
Upvotes: 2