【问题标题】:How does local() differ from other approaches to closure in R?local() 与 R 中的其他闭包方法有何不同?
【发布时间】:2011-12-15 15:49:39
【问题描述】:

昨天我从 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 中看到过这种事情,其中​​采用了不同的方法。

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

【问题讨论】:

    标签: r closures local static-variables


    【解决方案1】:

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

    getMPIcluster <- NULL
    setMPIcluster <- NULL
    local({
        cl <- NULL
        getMPIcluster <<- function() cl
        setMPIcluster <<- function(new) cl <<- new
    })
    

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

    使用函数创建闭包是一种工厂模式——R 文档简介中的 bank account 示例,其中每次调用 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 的数字表示的数字范围很小,因此可能会使用高效的预计算值查找表) .有趣的是,这里的爬虫是单例的;可以很容易地遵循工厂模式,因此每个基本 URL 一个爬虫。

    【讨论】:

    • local 方便的另一种模式是记忆。 R Inferno 中有一个例子。
    • 您认为爬虫是单例的想法很有趣,因为本地的替代方法是立即评估不带参数的匿名函数 - 工厂模式可能会在基本 url 上使用闭包。
    【解决方案2】:

    封装

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

    请注意,我们实际上可以访问隐藏功能,尽管它并不容易:

    # run hidden.fn
    environment(example)$hidden.fn()
    

    面向对象编程

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

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

    proto 不会隐藏 xfn,但它并不容易错误地访问它们,因为您必须使用 p$xp$fn() 来访问它们,这与能够编写 @ 并没有什么不同987654332@

    编辑:

    面向对象的方法确实增加了继承的可能性,例如可以定义p 的子代,其行为类似于p,只是它覆盖fn

    ch <- p$proto(fn = function(.) cat("Hello from ch\n")) # child
    ch$example() # prints: Hello from ch
    

    【讨论】:

      猜你喜欢
      • 2017-01-31
      • 1970-01-01
      • 2019-03-10
      • 1970-01-01
      • 2015-05-29
      • 1970-01-01
      • 1970-01-01
      • 2015-03-13
      • 2016-07-10
      相关资源
      最近更新 更多