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

发布于 2024-12-12 06:30:42 字数 822 浏览 2 评论 0 原文

昨天,我从 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"

我已经在 R 中的静态变量,其中采用了不同的方法。

这两种方法各有什么优缺点?

Yesterday I learned from Bill Venables how local() can help create static functions and variables, e.g.,

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")
  }
})

which behaves as follows from the command prompt:

> 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"

I've seen this kind of thing discussed in Static Variables in R where a different approach was employed.

What the pros and cons of these two methods?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

七禾 2024-12-19 06:30:42

封装

这种编程风格的优点是隐藏的对象不太可能被其他任何内容覆盖,因此您可以更加确信它们包含您的想法。它们不会被错误使用,因为它们不容易被访问。在问题的链接帖子中,有一个全局变量 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() 来访问它们,这与能够编写 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

Encapsulation

The advantage of this style of programming is that the hidden objects won't likely be overwritten by anything else so you can be more confident that they contain what you think. They won't be used by mistake since they can't readily be accessed. In the linked-to post in the question there is a global variable, count, which could be accessed and overwritten from anywhere so if we are debugging code and looking at count and see its changed we cannnot really be sure what part of the code has changed it. In contrast, in the example code of the question we have greater assurance that no other part of the code is involved.

Note that we actually can access the hidden function although its not that easy:

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

Object Oriented Programming

Also note that this is very close to object oriented programming where example and hidden.fn are methods and hidden.x is a property. We could do it like this to make it explicit:

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

proto does not hide x and fn but its not that easy to access them by mistake since you must use p$x and p$fn() to access them which is not really that different than being able to write e <- environment(example); e$hidden.fn()

EDIT:

The object oriented approach does add the possibility of inheritance, e.g. one could define a child of p which acts like p except that it overrides fn.

ch <- p$proto(fn = function(.) cat("Hello from ch\n")) # child
ch$example() # prints: Hello from ch
拥抱没勇气 2024-12-19 06:30:42

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

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

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

使用函数创建闭包是一种工厂模式——bank R 简介文档中的 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() can implement a singleton pattern -- e.g., the snow package uses this to track the single Rmpi instance that the user might create.

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

local() might also be used to manage memory in a script, e.g., allocating large intermediate objects required to create a final object on the last line of the clause. The large intermediate objects are available for garbage collection when local returns.

Using a function to create a closure is a factory pattern -- the bank account example in the Introduction To R documentation, where each time open.account is invoked, a new account is created.

As @otsaw mentions, memoization might be implemented using local, e.g., to cache web sites in a crawler

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())

(the usual example of memoization, Fibonacci numbers, is not satisfying -- the range of numbers that don't overflow R's numeric representation is small , so one would probably use a look-up table of efficiently pre-calculated values). Interesting how crawler here is a singleton; could as easily have followed a factory pattern, so one crawler per base URL.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文