Функция R для получения ссылки на переменную - PullRequest
0 голосов
/ 21 января 2020

В Advanced R среды объявляются как полезный способ получения семантики передачи по ссылке в R: вместо передачи списка, который копируется, я могу передать среду, которая не является , Это полезно знать.

Но предполагается, что тот, кто вызывает мою функцию, будет рад договориться о типе данных на основе "среды" с именованными слотами, соответствующими переменным, которые мы хотим изменить.

Разве кто-то не создал класс, который позволяет мне просто ссылаться на одну переменную по ссылке? Например,

v = 1:5
r <- ref(v)
(function() {
    getRef(r)       # same as v
    setRef(r, 1:6)  # same as v <<- 1:6, in this case
})()

Казалось бы, сделать это довольно просто, сохранив имя символа v вместе со средой, в которой он связан.

Существует ли стандартная библиотека которая выполняет эту семантику, или кто-то может предоставить короткий фрагмент кода? (Я еще не закончил читать «Advanced R»; извиняюсь, если об этом позже »)

Ответы [ 2 ]

2 голосов
/ 21 января 2020

Как вы уже упоминали в своем вопросе, вы можете сохранить имя переменной и окружение и получить к нему доступ с помощью get и assign, что будет как-то похоже на ссылка на одну переменную.

v <- 1:5
r <- list(name="v", env=environment())
(function() {
    get(r$name, envir = r$env)
    assign(r$name, 1:6, envir = r$env)
})()
v
#[1] 1 2 3 4 5 6

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

v <- 1:5
r <- globalenv() #reference to everything in globalenv
(function() {
    r$v
    r$v <- 1:6
})()
v
#[1] 1 2 3 4 5 6

Вы также можете создать среду только с одной переменной и сделать ссылку на нее.

v <- new.env(parent=emptyenv())
v$v <- 1:5
r <- v
(function() {
    r$v
    r$v <- 1:6
})()
v$v
#[1] 1 2 3 4 5 6

Реализовано как функции с использованием find или задать среду при создании. Посмотрите также Как получить переменную окружения в R .

ref <- function(name, envir = NULL) {
  name <- substitute(name)
  if (!is.character(name)) name <- deparse(name)
  if(length(envir)==0) envir <- as.environment(find(name))
  list(name=name, envir=envir)
}
getRef <- function(r) {
  get(r$name, envir = r$envir, inherits = FALSE)
}
setRef <- function(r, x) {
  assign(r$name, x, envir = r$envir, inherits = FALSE)
}

x <- 1
r1 <- ref(x) #x from Global Environment

#x from Function Environment
r2 <- (function() {x <- 2; ref(x, environment())})()
#But simply returning x might here be better
r2b <- (function() {x <- 2; x})()

a <- new.env(parent=emptyenv())
a$x <- 3
r3 <- ref(x, a) #x from Environment a
0 голосов
/ 25 января 2020

Это основано на ответе GKi, спасибо ему за повышение.

  • Включает pryr :: where, поэтому вам не нужно устанавливать всю библиотеку
  • Примечание что нам нужно указать «где» на parent.frame() в определении «ref»
  • Добавлены некоторые тестовые случаи, которые я использовал для проверки правильности

Код:

# copy/modified from pryr::where
where = function(name, env=parent.frame()) {
  if (identical(env, emptyenv())) {
    stop("Can't find ", name, call. = FALSE)
  }
  if (exists(name, env, inherits = FALSE)) {
    env
  } else {
    where(name, parent.env(env))
  }
}

ref <- function(v) {
  arg <- deparse(substitute(v))
  list(name=arg, env=where(arg, env=parent.frame()))
}

getRef <- function(r) {
  get(r$name, envir = r$env, inherits = FALSE)
}

setRef <- function(r, x) {
  assign(r$name, x, envir = r$env)
}

if(1) { # tests
  v <- 1:5
  r <- ref(v)
  (function() {
    stopifnot(identical(getRef(r),1:5))
    setRef(r, 1:6)
  })()
  stopifnot(identical(v,1:6))

  # this refers to v in the global environment
  v=2; r=(function() {ref(v)})()
  stopifnot(getRef(r)==2)
  setRef(r,5)
  stopifnot(getRef(r)==5)
  stopifnot(v==5)

  # same as above
  v=2; r=(function() {v <<- 3; ref(v)})()
  stopifnot(getRef(r)==3)
  setRef(r,5)
  stopifnot(getRef(r)==5)
  stopifnot(v==5)

  # this creates a local binding first, and refers to that. the
  # global binding is unaffected
  v=2; r=(function() {v=3; ref(v)})()
  stopifnot(getRef(r)==3)
  setRef(r,5)
  stopifnot(getRef(r)==5)
  stopifnot(v==2)

  # additional tests
  r=(function() {v=4; (function(v1) { ref(v1) })(v)})()
  stopifnot(r$name=="v1")
  stopifnot(getRef(r)==4)
  setRef(r,5)
  stopifnot(getRef(r)==5)

  # check that outer v is not modified
  v=2; r=(function() {(function(v1) { ref(v1) })(v)})()
  stopifnot(getRef(r)==2)
  setRef(r,5)
  stopifnot(getRef(r)==5)
  stopifnot(v==2)
}

Я полагаю, что может быть некоторая неэффективность сборки мусора, если вы создаете ссылку на небольшую переменную во временной среде с другой большой переменной, поскольку ссылка должна сохранять всю среду - хотя та же проблема может возникают при других применениях лексической области видимости.

Возможно, я буду использовать этот код в следующий раз, когда мне понадобится семантика передачи по ссылке.

...