Как добавить функции в цикле в R6Class в R - PullRequest
1 голос
/ 17 мая 2019

Я хочу написать обертку поверх R6Class, но она не работает, я попробовал несколько вещей после того, как нашел этот вопрос Динамически добавить функцию в экземпляр класса r6

Итак, я попробовал это, они оба не работают:

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

это из-за зацикливания с замыканиями, поскольку for не создает новую область видимости. Итак, я попробовал это:

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()

это выдаст ошибку, что имя не определено, потому что это поведение R6Class, что все находится в eval substitute, поэтому нет способа создать новую функцию, которая берет область / среду, откуда она была вызвана. Или есть способ?

Моя настоящая проблема в том, что я хочу создать упаковщик функций и хочу вызвать:

fn <- function() {
    x <- 10
    y <- myFunction(public = list(
       foo = function(y) {
          x + y
       }
    })
    z <- y$new()
    z$foo(10)
    ## I want 20 as result
}

Есть ли способ создать myFunction функцию, которая создаст R6Class? Причина, по которой я этого хочу, заключается в том, что у меня есть система компонентов, основанная на R6Class, и я хочу удалить шаблон, который необходимо добавить к каждому классу, чтобы его было проще использовать. Я не хочу создавать новую систему классов, я хотел бы использовать классы R6.

1 Ответ

1 голос
/ 17 мая 2019

Я спросил на GitHub после добавления этого вопроса, и они дали ответ очень быстро. Вот повторный пост ответа:

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()

и если вы хотите получить более подходящее имя при печати x $ a, вы можете очистить имя ref используя:

attr(fn, "srcref") <- NULL

EDIT

и вот пример, когда значения, добавленные в класс, являются функциями (это мой улучшенный код):

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()

и если вы хотите иметь доступ к супер, вам нужно использовать этот код:

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]])
    }
  })
}

вместо env$self <- parent$self вы также можете использовать get("self", parent) (он будет искать переменную в цепочке окружения).

...