local()与R中的其他闭包方法有何不同?

Dav*_*ell 16 closures r local static-variables

昨天我从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")
  }
})
Run Code Online (Sandbox Code Playgroud)

从命令提示符的行为如下:

> 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"
Run Code Online (Sandbox Code Playgroud)

我已经在R中的静态变量中讨论过这种事情,其中采用了不同的方法.

这两种方法的优缺点是什么?

G. *_*eck 12

封装

这种编程风格的优点是隐藏的对象不会被其他任何东西覆盖,因此您可以更自信地包含您的想法.它们不会被错误地使用,因为它们不容易被访问.在问题中的链接帖子中有一个全局变量,count可以从任何地方访问和覆盖,所以如果我们调试代码并查看count并查看其更改,我们无法确定代码的哪个部分已更改它.相反,在问题的示例代码中,我们更加确信不涉及代码的其他部分.

请注意,我们实际上可以访问隐藏的功能,虽然它不是那么容易:

# run hidden.fn
environment(example)$hidden.fn()
Run Code Online (Sandbox Code Playgroud)

面向对象编程

还要注意,这非常接近于面向对象的编程,其中examplehidden.fn是方法并且hidden.x是属性.我们可以这样做,使其明确:

library(proto)
p <- proto(x = "x", 
  fn = function(.) cat(' "fn()"\n '),
  example = function(.) .$fn()
)
p$example() # prints "fn()"
Run Code Online (Sandbox Code Playgroud)

proto不会隐藏x,fn但它不容易错误地访问它们因为你必须使用p$xp$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
Run Code Online (Sandbox Code Playgroud)


Mar*_*gan 6

local()可以实现单例模式 - 例如,snow包使用它来跟踪用户可能创建的单个Rmpi实例.

getMPIcluster <- NULL
setMPIcluster <- NULL
local({
    cl <- NULL
    getMPIcluster <<- function() cl
    setMPIcluster <<- function(new) cl <<- new
})
Run Code Online (Sandbox Code Playgroud)

local()也可用于管理脚本中的内存,例如,分配在子句的最后一行上创建最终对象所需的大型中间对象.返回时,大型中间对象可用于垃圾回收local.

使用函数创建闭包是工厂模式 - Introduction to R文档中的银行帐户示例,每次open.account调用时,都会创建一个新帐户.

正如@otsaw所提到的,可以使用本地实现memoization,例如,在爬虫中缓存网站

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())
Run Code Online (Sandbox Code Playgroud)

(通常的记忆例子,Fibonacci数字,并不令人满意 - 没有溢出R的数字表示的数字范围很小,所以人们可能会使用有效预先计算值的查找表).有趣的是这里的爬行者是单身人士; 可以很容易地遵循工厂模式,因此每个基本URL都有一个爬虫.