昨天我从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.xn")
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"
我在R中的静态变量中看到过这种讨论,其中采用了不同的方法。
这两种方法的优缺点是什么?
封装
这种编程风格的优点是隐藏对象不太可能被其他任何东西覆盖,因此您可以更确信它们包含您认为的内容。它们不会被误用,因为它们不容易被访问。在问题的链接帖子中有一个全局变量,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 chn")) # child
ch$example() # prints: Hello from ch
local()
可以实现一个单例模式——例如,snow
包使用它来跟踪用户可能创建的单个Rmpi实例。
getMPIcluster <- NULL
setMPIcluster <- NULL
local({
cl <- NULL
getMPIcluster <<- function() cl
setMPIcluster <<- function(new) cl <<- new
})
local()
也可以用于管理脚本中的内存,例如,分配在子句的最后一行创建最终对象所需的大型中间对象。当local
返回时,大的中间对象可用于垃圾收集。
使用函数创建闭包是一种工厂模式——在介绍R文档中的银行帐户示例中,每次调用open.account
时,都会创建一个新帐户。
正如@otsaw提到的,记忆可以使用本地实现,例如,在爬虫中缓存网站
library(XML)
crawler <- local({
seen <- new.env(parent=emptyenv())
.do_crawl <- function(url, base, pattern) {
if (!exists(url, seen)) {
message(url)
xml <- htmlTreeParse(url, useInternal=TRUE)
hrefs <- unlist(getNodeSet(xml, "//a/@href"))
urls <-
sprintf("%s%s", base, grep(pattern, hrefs, value=TRUE))
seen[[url]] <- length(urls)
for (url in urls)
.do_crawl(url, base, pattern)
}
}
.do_report <- function(url) {
urls <- as.list(seen)
data.frame(Url=names(urls), Links=unlist(unname(urls)),
stringsAsFactors=FALSE)
}
list(crawl=function(base, pattern="^/.*html$") {
.do_crawl(base, base, pattern)
}, report=.do_report)
})
crawler$crawl(favorite_url)
dim(crawler$report())
(通常的记忆示例,斐波那契数,并不令人满意——不会溢出R的数字表示的数字范围很小,因此可能会使用有效预计算值的查找表)。有趣的是,这里的crawler是一个单例;可以很容易地遵循工厂模式,因此每个基本URL一个爬虫。