r-lib / rlang

Low-level API for programming with R
https://rlang.r-lib.org
Other
508 stars 138 forks source link

Deprecate `with_env()` and `locally()` #1306

Closed pnacht closed 3 years ago

pnacht commented 3 years ago

with_env allows us to evaluate expressions in an environment, including setting values in the environment:

e <- new.env()

rlang::with_env(e, {
  x <- 1
  y <- 2
  f <- function(x) print(x)
  g <- function() f(1)
})

e$x
#> [1] 1
e$g()
#> [1] 1

However, it doesn't support quasiquotation, which means we can't trivially write a function which takes an expression and evaluates it in an environment.

set_in_env <- function(expr) {
  e <- new.env()

  q <- rlang::enquo(expr)

  rlang::with_env(e, !!q)
  rlang::with_env(e, {x <- 1})

  return(e)
}

e <- set_in_env({y <- 2})
#> Error: Quosures can only be unquoted within a quasiquotation context. [...]

Created on 2021-10-26 by the reprex package (v2.0.1)

As mentioned by @MrFlick on SO, we can use inject to build the call:

set_in_env <- function(expr) {  
  e <- new.env()

  q <- rlang::enexpr(expr)

  rlang::inject(rlang::with_env(e, !!q))
  rlang::with_env(e, {x <- 1})

  return(e)
}

This works, but it feels like a workaround and not like the most proper and idiomatic way of doing it. Would allowing unquotation in with_env be possible or is there a reason for this that hasn't occurred to me?

lionel- commented 3 years ago

As documented, with_env() is experimental. I think I'll just deprecate it now.

Note that if you want to use quosures, you'll need the environment to be a data mask. And use eval_tidy(), which with_env() doesn't.

set_in_env <- function(expr) {
  expr <- enquo(expr)

  expr <- expr({
    !!expr
    x <- 1
    environment()
  })

  eval_tidy(expr)
}

e <- set_in_env(foo <- 2)
env_print(e)
#> <environment: 0x7fca55826510>
#> Parent: <environment: 0x7fca46733450>
#> Bindings:
#> • foo: <dbl>
#> • `~`: <fn>
#> • x: <dbl>
#> • .__tidyeval_quosure_mask__.: <env>

A data mask does not feel appropriate for this sort of function, so I'd remove quosures from the equation:

set_in_env <- function(expr, parent = caller_env()) {
  expr <- enexpr(expr)

  env <- env(parent, x = 1)
  eval_bare(expr, env)

  env
}

e <- set_in_env(foo <- 2)
env_print(e)
#> <environment: 0x7fca37d9c8f8>
#> Parent: <environment: global>
#> Bindings:
#> • x: <dbl>
#> • foo: <dbl>

By the way do you know about env_bind()?

lionel- commented 3 years ago

Reopening so I don't forget to deprecate.