Home > other >  R: In Non-standardard evaluation (NSE), how do I determine the right calling environment when using
R: In Non-standardard evaluation (NSE), how do I determine the right calling environment when using

Time:01-26

Consider

proper_filter <- function(.data, ...) {
  code = substitute(filter(.data, ...))
  print(names(parent.frame()))
}

fn_wo_magrittr <- function(dfr, val) {
  proper_filter(dfr, speed < val) 
}

fn_w_magrittr <- function(dfr, val) {
  dfr %>% 
    proper_filter(speed < val) 
}

val2 = 10
fn_wo_magrittr(cars, val2)
fn_w_magrittr(cars, val2)

The function just prints the names defined in the parent.frame() when proper_filter is called.

The fn_wo_magrittr doesn't use {magrittr}, so it identifies the right parent.frame() which has dfr and val defined.

But the function fn_wo_magrittr uses {magrittr} and so the parent.frame() is not directly the calling environment, but is probably changed by the %>%.

How do I find the proper calling environment? The one where %>% was called. I can't seem to figure this out.

CodePudding user response:

What you are trying to do is probably not safe. As far as I know, magrittr doesn't document what stack frames look like when it is running, so while you might get it to work, it won't necessarily continue to work with the next magrittr release.

Instead, you should use the R pipe, |>. It is documented to do very simple syntax manipulation, so

dfr |>
    proper_filter(speed < val) 

is documented to be the same as

proper_filter(dfr, speed < val)

There are a few disadvantages to the R pipe: it is only available in R 4.1.0 or newer, and it is more limited, e.g. . is not supported as a place holder, so perhaps your best approach is not to use pipes at all.

CodePudding user response:

1) In general, it is safer and more flexible to pass the environment to functions using environments. One can still use parent.frame() as the default but using an explicit argument allows the general situation to be handled.

library(magrittr)
val2 <- 10

proper_filter2 <- function(.data, ..., envir = parent.frame()) {
  code <- substitute(filter(.data, ...))
  print(ls(envir))
}

fn_w_magrittr2 <- function(dfr, val, .envir = environment()) {
  dfr %>% 
    proper_filter2(speed < val, envir = .envir)
}

fn_w_magrittr2(cars, val2)
## [1] "dfr"   "val"  

2) Another way to handle this is to use formulas since they have environments. The formula argument of proper_filter3 should be a one sided formula.

library(magrittr)
val2 <- 10

proper_filter3 <- function(.data, formula) {
  code <- substitute(filter(.data, rhs), list(rhs = formula[[2]]))
  print(ls(environment(formula)))
}

fn_w_magrittr3 <- function(dfr, val) {
  .formula <- ~ speed < val
  dfr %>% 
    proper_filter3(.formula)
}

fn_w_magrittr3(cars, val2)
## [1] "dfr"     "val"   

There is some discussion of this in the magrittr issues in https://github.com/tidyverse/magrittr/issues/171, https://github.com/tidyverse/magrittr/issues/38 and https://github.com/tidyverse/magrittr/issues/70 and it seems that they concluded that this cannot be resolved within magrittr itself without sacrificing important features of magrittr.

The above show how to handle this while still using magrittr but workarounds replacing magrittr are possible too. Another answer mentions |> and the Bizarro pipe can be used for another workaround and not using pipes is, of course, always a possibility.

  •  Tags:  
  • Related