【问题标题】:About lexical scoping in R关于 R 中的词法作用域
【发布时间】:2013-06-26 10:29:30
【问题描述】:

我对 R 相当陌生,在阅读手册时,我偶然发现了一段关于词法作用域的文章以及以下代码示例:

 open.account <- function(total) {
   list(
     deposit = function(amount) {
       if(amount <= 0)
         stop("Deposits must be positive!\n")
       total <<- total + amount
       cat(amount, "deposited.  Your balance is", total, "\n\n")
     },
     withdraw = function(amount) {
       if(amount > total)
         stop("You don't have that much money!\n")
       total <<- total - amount
       cat(amount, "withdrawn.  Your balance is", total, "\n\n")
     },
     balance = function() {
       cat("Your balance is", total, "\n\n")
     }
   )
 }

 ross <- open.account(100)
 robert <- open.account(200)

 ross$withdraw(30)
 ross$balance()
 robert$balance()

 ross$deposit(50)
 ross$balance()
 ross$withdraw(500)

所以,我明白上面的代码是做什么的,我想我仍然对它的工作原理感到困惑。如果在函数执行完成后您仍然可以访问函数的“局部”变量,那么预测何时不再需要变量不是很难或不可能吗?在上面的代码中,如果它被用作更大程序的一部分,“total”是否会保存在内存中,直到整个程序完成?(本质上成为一个全局变量内存)如果这是真的,不会这会导致内存使用问题吗?

我在这个网站上查看了另外两个问题:“词法作用域是如何实现的?”和“为什么编译器更喜欢词法范围?”。那里的答案在我脑海中浮现,但它让我想知道:如果(正如我猜测的那样)编译器不仅使所有变量全局化(内存方面),而是使用某种技术来预测某些变量何时不会不再需要并且可以删除,这样做实际上不会使编译器更难而不是更容易吗?

我知道这是很多不同的问题,但任何帮助都会很好,谢谢。

【问题讨论】:

  • total 在此实例中是一个全局变量,因为您使用了&lt;&lt;- 运算符,请参阅?"&lt;&lt;-"。您应该能够在交互式会话中直接检查total。相反,如果您使用&lt;-,它在函数执行后将不可用,但它也会破坏这个特定的函数。
  • R 确实将其所有变量都存储在内存中。这确实会导致大型数据集的内存问题。这就是为什么会有像bigdatabigmemoryffdata.table 等这样的包来解决 R 的一些设计限制。您可以做的其他事情是将数据集存储在数据库中,并仅在必要时查询它们;见RODBCDBIRMySQLSQLite等等。
  • @Justin Umm 不太清楚你的意思,但 R 帮助文档说
  • @HongOoi 哦,好吧,我想这回答了我想知道的主要问题。谢谢!
  • @Katana,基本上,为评估函数“调用”而创建的环境只有在评估完成后没有对对象的引用时才会被破坏。有关更多信息,请参阅github.com/hadley/devtools/wiki/Environments 和相关链接。

标签: r variables scope lexical lexical-scope


【解决方案1】:

您应该将open.account 视为生成“帐户”的命名个体实例的生成器函数。每个单独的帐户都有一个本地“总计”和一组对该特定总计进行操作的函数。 (没有编译器;R 被解释。)局部变量 'total' 会占用空间,直到持有它的对象被删除。我不认为“全球”是谈论这个的好方法(尽管帮助页面中的语言。)。如果您在命令行中(即“查看”.GlobalEnv)并执行了ls() 调用,您将看不到任何未结账户“总计”。

如果你想创建一个代码可检查的版本,@G. Grothendieck 在 2011 年在 R-help 中建议的策略可能会很有趣:

open.account <- function(total) {
   this<-environment()
   list(this,
     deposit = function(amount) {
       if(amount <= 0)
         stop("Deposits must be positive!\n")
       total <<- total + amount
       cat(amount, "deposited.  Your balance is", total, "\n\n")
     },
     withdraw = function(amount) {
       if(amount > total)
         stop("You don't have that much money!\n")
       total <<- total - amount
       cat(amount, "withdrawn.  Your balance is", total, "\n\n")
     },
     balance = function() {
       cat("Your balance is", total, "\n\n")
     }
   )
 }

 ross <- open.account(100)
ross$deposit(200)
ross[[1]]$total
[1] 300

如果您命名了第一个列表元素。 'this' 你可以这样做:

> ross$deposit(200)
200 deposited.  Your balance is 300 

> ross$this$total
[1] 300

很长一段时间以来,我一直对“词汇”这个词感到困惑。很长一段时间我都想不通它为什么叫这个名字。最终,我想到了一个字典类比,也许有不同版本的字典。一个词在某个特定的出版时间从其他词集合中的定义中获得意义。如果出版了新词典,其含义可能会发生变化,但研究与第一版同时出版的材料的人应该根据较早的词典版本而不是较新的词典来解释它。

【讨论】:

  • 好吧,我的主要困惑是,似乎抽象的“帐户”和“总”的单个实例实际上都没有名称。这些名称指的是 open.account 返回的三个函数。似乎没有任何“对象”可以保持全部。但是我从你的话中收集到的是,尽管“total”没有对人类可见的引用,但 R 解释器仍然知道何时不再需要它?例如,如果将 ross 设置为与 open.account 无关的其他内容,它的 total 实例会从内存中删除吗?
  • 是的。如果 'ross' 被从工作空间中删除,或者该名称被分配给其他东西,那么保存 'total' 的内存和本地函数都将是垃圾回收的。
  • 但是,说它是可垃圾回收的,并不一定意味着内存碎片可能不会持续存在。有时“可用内存”会很大,但最大的连续空间可能比“可用”in_toto 小很多。
  • 好的,我想我现在明白了,谢谢。回应你的“词汇”类比。我自己对这个词的解释是,因为词汇意味着“与词相关”。词法作用域以与源代码中的文本和单词直接相关的方式做事(与执行时发生的事件相反)。前任。声明的变量在其声明的 w/e 函数的文本中具有精确的范围,使用
  • 我认为&lt;&lt;- 在膜外弹出一个值(在生物学意义上)。我经常遇到的困难是,一个细胞(或相邻的细胞,或者身体器官中可能有不同的细胞类型)中可能有更多的细胞器,因此很难准确跟踪哪个膜包围了手术。在这种情况下,&lt;&lt;- 的使用将特定调用的局部函数之外的重新计算值弹出到(细胞质?)下一个“向上”级别。
【解决方案2】:

OP 似乎正在寻找有关环境的说明。

在 R 中,每个函数[1] 都有一个封闭的环境。这是它所知道的对象的集合,除了作为其参数传入的对象或在其代码中创建的对象。

当你在提示符下创建一个函数时,它的环境是全局环境。这只是您工作区中的对象集合,您可以通过键入ls() 来查看。例如,如果您的工作区包含一个数据框Df,您可以创建如下函数:

showDfRows <- function()
{
    cat("The number of rows in Df is: ", nrow(Df, "\n")
    return(NULL)
}

您的函数知道Df,即使您没有将它作为参数传递;它存在于函数的环境中。环境可以嵌套,这就是包命名空间之类的工作方式。例如,您可以使用lm(y ~ x, data=Df) 来拟合回归,即使您的工作区不包含任何名为lm 的对象。这是因为全局环境的父链包含stats 包,这是lm 函数所在的位置。[2]

当函数在另一个函数中创建时,它们的封闭环境是它们父函数的求值框架。这意味着子函数可以访问父函数已知的所有对象。例如:

f <- function(x)
{
    g <- function()
    {
        cat("The value of x is ", x, "\n")
    }
    return(NULL)
}

请注意,g 不包含任何名为 x 的对象,其任何参数也不包含名为 x 的任何参数。但是,这一切仍然有效,因为它将从其父 f 的评估框架中检索 x

这是上面代码使用的技巧。当您运行open_account 时,它会创建一个评估框架来执行其代码。 open_account 然后创建 3 个函数,depositwithdrawbalance。这 3 个中的每一个都具有 open_account 的评估框架作为其封闭环境。在这个评估框架中有一个名为total的变量,它的值是你传入的,将被depositwithdrawbalance操作。

open_account 完成时,它会返回一个列表。如果这是一个常规函数,它的评估框架现在将由 R 处理。然而,在这种情况下,R 可以看到返回的列表包含需要使用该评估框架的函数;所以框架继续存在。

那么,为什么罗斯和罗伯特的账户不冲突呢?每次执行 open_account 时,R 都会创建一个 new 评估框架。打开罗斯和罗伯特账户的框架是完全分开的,就像,如果你运行lm(y ~ x, data=Df),如果你运行lm(y ~ x, data=Df2,就会有一个单独的框架)。每次open_account 返回时,它都会带来一个新的环境来存储刚刚创建的余额。 (它包含depositwithdrawbalance函数的新副本,但通常我们可以忽略用于此的内存。)

[1] 从技术上讲,每次关闭,但我们不要搞混

[2] 同样,命名空间和环境之间存在技术区别,但在这里并不重要

【讨论】:

  • 啊,是的,这确实是我想要的。谢谢!还要感谢 Ferdinand.kraft(评论链接也很有帮助)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-21
  • 1970-01-01
  • 2018-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-03
相关资源
最近更新 更多