简体   繁体   中英

How to achieve block scoping in R?

I am thinking of ways to achieve block scoping in R. This would be nice for keeping a clean workspace in data science notebooks/interactive sessions. At the moment I am using an IIFE pattern like so

(function(){
temp1 <- ...
temp2 <- ...
temp3 <- ...

data <<- fn(temp1, temp2, temp3)
})()

This way I can create/update data and let the temporary be cleaned up after me. Obviously it still has side-effects with regards to potentially assigning to global, but for data analysis and not software packages I'm not concerned.

Until IIFE becomes more popular in RI thought it'd be neat to have a special operator for this, but I don't know enough about R metaprogramming. In my naive head the following should have been sufficient

`%gets%` <- function(x, val) {
    val <- local(val)
    assign(deparse(substitute(x)), val, envir = parent.frame())
}

x1 %gets% {
    x = 10;
    x + 5
}

But x still get dumped out to my global scope. So

  1. Is this a reasonable implementation for simulating block scoping?
  2. If so, how can I prevent my x from escaping to the outside scope?

1) local First note that this works:

if (exists("x")) rm(x) # just for reproducibility.  Don't need this normally.
x1 <- local({ x <- 10; x + 5})

x1
## [1] 15

x
## Error: object 'x' not found

2) %gets% To implement %gets% we can use substitute like this:

`%gets%` <- function(.x, .value) {
  assign(deparse(substitute(.x)), eval.parent(substitute(local(.value))), parent.frame())
}

x1 %gets% {
    x = 10;
    x + 5
}

x1
## [1] 15

x
## Error: object 'x' not found

2a) := We can make this even nicer by defining := like this:

`:=` <- `%gets%`

# test
x1 := { x <- 10; x + 5}

x1
## [1] 15

x
## Error: object 'x' not found

3) pipes Also piping can be used to avoid globals. Here x and y do not persist after the pipe completes.

library(magrittr)

list(x = 6) %$% { y <- 1; x + y + 5 }
## [1] 12

x
## Error: object 'x' not found

y
## Error: object 'y' not found

or if we have nothing to pass:

x1 <- list() %>% { x <- 10; x + 5 }

x1
## [1] 15

x
## Error: object 'x' not found

or we could use 0 to save keystrokes:

x1 <- 0 %>% { x <- 10; x + 5 }

Update Have revised (2) to simplify and correct it. Also Added (2a) and (3).

local does what you want (and IIFE is a hack in JavaScript to work around the lack of a local -like functionality).

Your %gets% code fails because you're misunderstanding how arguments are evaluated: in your function, val is an argument. This means that it is evaluated in the caller's scope , no exceptions. Wrapping it in local simply means that the result of evaluating val is wrapped in local — ie meaningless in this case. It does not mean that the expression is evaluated locally; if that were the case, you wouldn't need local at all, you could just evaluate it in the function's scope.

You can do that, if you want, by using eval :

`%gets%` = function (x, expr) {
    assign(
        as.character(substitute(x)),
        eval(substitute(expr)),
        parent.frame()
    )
}

… but that won't be very useful, since it cannot access variables of the caller's scope; rather, you'd have to evaluate it in a scope that injects the caller's scope, so that you have a “clean” environment yet can access existing variables:

`%gets%` = function (x, expr) {
    parent = parent.frame()
    assign(
        as.character(substitute(x)),
        eval.parent(substitute(eval(quote(expr), new.env(parent = parent)))),
        parent
    )
}

… but this is essentially just a convoluted way of redefining local assignment.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM