昨天我从Bill Venables那里了解到local()如何帮助创建静态函数和变量,例如,
example <- local({
hidden.x <- "You can't see me!"
hidden.fn <- function(){
cat("\"hidden.fn()\"")
}
function(){
cat("You can see and call example()\n")
cat("but you can't see hidden.x\n")
cat("and you can't call ")
hidden.fn()
cat("\n")
}
})
在命令提示符下的行为如下:
> ls()
[1] "example"
> example()
You can see and call example()
but you can't see hidden.x
and you can't call "hidden.fn()"
> hidden.x
Error: object 'hidden.x' not found
> hidden.fn()
Error: could not find function "hidden.fn"
我在Static Variables in R中讨论过这种事情,采用了不同的方法。
这两种方法的优点和缺点是什么?
答案 0 :(得分:12)
<强>封装强>
这种编程风格的优点是隐藏的对象不会被其他任何东西覆盖,因此您可以更自信地包含您的想法。它们不会被错误地使用,因为它们不容易被访问。在问题中的链接帖子中有一个全局变量count
,可以从任何地方访问和覆盖,所以如果我们正在调试代码并查看count
并看到它已更改,我们真的不能确保代码的哪一部分已经改变了它。相反,在问题的示例代码中,我们更加确信不会涉及代码的其他部分。
请注意,我们实际上可以访问隐藏的功能,虽然它并不那么容易:
# run hidden.fn
environment(example)$hidden.fn()
面向对象编程
另请注意,这与面向对象编程非常接近,其中example
和hidden.fn
是方法,hidden.x
是属性。我们可以这样做,使其明确:
library(proto)
p <- proto(x = "x",
fn = function(.) cat(' "fn()"\n '),
example = function(.) .$fn()
)
p$example() # prints "fn()"
proto不会隐藏x
和fn
,但错误地访问它们并不容易,因为您必须使用p$x
和p$fn()
来访问它们与写e <- environment(example); e$hidden.fn()
编辑:
面向对象的方法确实增加了继承的可能性,例如:可以定义p
的子项,其行为类似于p
,但它会覆盖fn
。
ch <- p$proto(fn = function(.) cat("Hello from ch\n")) # child
ch$example() # prints: Hello from ch
答案 1 :(得分:6)
local()
可以实现单例模式 - 例如,snow
包使用它来跟踪用户可能创建的单个Rmpi实例。
getMPIcluster <- NULL
setMPIcluster <- NULL
local({
cl <- NULL
getMPIcluster <<- function() cl
setMPIcluster <<- function(new) cl <<- new
})
local()
也可用于管理脚本中的内存,例如,分配在子句的最后一行上创建最终对象所需的大型中间对象。 <{1}}返回时,大型中间对象可用于垃圾收集。
使用函数创建闭包是工厂模式 - Introduction to R文档中的bank account示例,每次调用local
时,都会创建一个新帐户。
正如@otsaw所提到的,可以使用本地实现memoization,例如,在抓取工具中缓存网站
open.account
(通常的记忆例子,Fibonacci数字,不满意 - 不溢出R的数字表示的数字范围很小,所以人们可能会使用有效预先计算值的查找表) 。有趣的是这里的爬行者是单身人士;可以很容易地遵循工厂模式,因此每个基本URL都有一个爬虫。