【问题标题】:R function for obtaining a reference to a variable用于获取对变量的引用的 R 函数
【发布时间】:2020-05-07 04:37:28
【问题描述】:

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 的字符名称与其绑定的环境一起存储。

是否有一个标准库可以实现这种语义,或者有人可以提供一个简短的 sn-p 代码吗? (我还没有读完“高级 R”;如果本书后面涉及到这一点,我们深表歉意)

【问题讨论】:

  • 什么是setRefgetRef?请添加所有相关代码。这本书将来可能会改变。
  • 由于您没有提到 data.table 包或 R6 类,我认为这些可能是有用的调查领域。

标签: r scope reference


【解决方案1】:

正如您在问题中已经提到的,您可以存储变量名及其环境并使用getassign访问它会以某种方式就像一个对单个变量的引用。

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 实现为函数或在创建期间设置环境。也可以看看How to get environment of a variable in 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

【讨论】:

  • 谢谢,我不知道as.environment(find(...))
  • 其实这里用“find”是错误的,它在一个包中寻找一个符号。我想我会回答我自己的问题,因为它需要一些额外的努力才能使代码正常工作。
【解决方案2】:

这是基于 GKi 的回答,感谢他加紧。

  • 它包括 pryr::where,因此您不必安装整个库
  • 注意,我们需要在“ref”的定义中将“where”指向parent.frame()
  • 添加了一些我用来检查正确性的测试用例

代码:

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

我想如果您在具有不同大变量的临时环境中创建对小变量的引用,我想可能会有一些垃圾收集效率低下,因为引用必须保留整个环境 - 尽管其他可能会出现同样的问题词法作用域的使用。

下次我需要传递引用语义时,我可能会使用此代码。

【讨论】:

  • 当变量从存储的环境中删除时,链接将指向parrent。试试看:v &lt;- 2; r &lt;- (function() {v &lt;- 3; ref(v)})(); getRef(r); rm(list=r$name, envir = r$env); getRef(r)
  • 如何在环境中设置对变量的引用?例如。 a &lt;- new.env(parent=emptyenv()); a$x &lt;- 3如何在a中设置为x
  • 这是一个功能请求,就像您希望 ref 有一个用于指定环境的可选参数一样?但也许它也应该返回一个带有打印功能的类等等......我认为这里的内容足以提供一个 SO 答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-03-09
  • 2016-01-08
  • 1970-01-01
  • 1970-01-01
  • 2020-12-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多