2011-10-26 6 views
16

Ayer aprendí de Bill Venables hacer local() puede ayudar a crear funciones y variables estáticas, por ejemplo,¿Cómo se diferencia local() de otros enfoques de cierre en R?

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

que se comporta de la siguiente manera desde el símbolo del sistema:

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

que he visto este tipo de cosa discutida en Static Variables in R donde se empleó un enfoque diferente.

¿Cuáles son los pros y los contras de estos dos métodos?

Respuesta

12

encapsulación

La ventaja de este estilo de programación es que los objetos ocultos, no es probable que se sobrescriben con cualquier otra cosa que usted pueda estar más seguro de que contienen lo que piensa. No se usarán por error ya que no se puede acceder fácilmente. En la publicación vinculada a la pregunta hay una variable global, count, que se puede acceder y sobrescribir desde cualquier lugar, por lo que si estamos depurando el código y mirando count y vemos su cambio, no podemos estar realmente seguros de qué parte del código tiene lo cambió Por el contrario, en el código de ejemplo de la pregunta, tenemos una mayor seguridad de que no está involucrada ninguna otra parte del código.

Tenga en cuenta que en realidad podemos acceder a la función oculta, aunque no es tan fácil:

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

Programación Orientada a Objetos

También tenga en cuenta que esto está muy cerca de programación orientada a objetos, donde example y hidden.fn son métodos y hidden.x es una propiedad. Podríamos hacerlo de esta manera para que sea explícito:

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

proto no oculta x y fn pero no es tan fácil acceder a ellos por error ya que se debe utilizar p$x y p$fn() para acceder a ellos, que no es realmente tan diferente de ser capaz de escribir e <- environment(example); e$hidden.fn()

EDIT:

el enfoque orientado a objetos se le añade la posibilidad de herencia, por ejemplo, uno podría definir un hijo de p que actúa como p excepto que anula fn.

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

local() puede implementar un patrón singleton - por ejemplo, el paquete snow utiliza esto para realizar un seguimiento de la instancia única RMPI que el usuario podría crear.

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

local() también podrían utilizarse para gestionar la memoria en una secuencia de comandos, por ejemplo, la asignación de grandes objetos intermedios necesarios para crear un objeto final en la última línea de la cláusula. Los objetos intermedios grandes están disponibles para la recolección de basura cuando devuelve local.

El uso de una función para crear un cierre es un patrón de fábrica: el ejemplo bank account en la documentación de Introducción a R, donde cada vez que se invoca open.account, se crea una nueva cuenta.

Como @otsaw menciones, memoization podría ser implementado utilizando local, por ejemplo, a los sitios web de caché en un rastreador

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

(el ejemplo usual de memoization, los números de Fibonacci, no es satisfactoria - la gama de números que no se desbordan La representación numérica de R es pequeña, por lo que uno probablemente usaría una tabla de búsqueda de valores precalculados de manera eficiente). Interesante cómo el rastreador aquí es un singleton; podría haber seguido tan fácilmente un patrón de fábrica, por lo que un rastreador por URL base.

+1

Otro patrón que 'local' hace conveniente es la memorización. Hay un ejemplo en algún lugar de The R Inferno. – otsaw

+0

Su idea de que crawler sea singleton es interesante porque una alternativa a lo local es evaluar inmediatamente una función anónima sin argumentos: un patrón de fábrica podría utilizar un cierre sobre la url base. – hadley

Cuestiones relacionadas