1

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.

xiaodai
  • 14,889
  • 18
  • 76
  • 140

2 Answers2

1

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.

user2554330
  • 37,248
  • 4
  • 43
  • 90
1

1) explicitly pass environment 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) formulas 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"   

3) pipe_eager_lexical The magrittr package contains a pipe_eager_lexical which is currently not assigned to a infix pipe symbol. It will be defined in the next version of magrittr as seen in this commit but in the meantime we can assign it to an infix symbol ourself.

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

`%!>%` <- magrittr::pipe_eager_lexical   # <------- note this definition
fn_w_magrittr4 <- function(dfr, val) {
  dfr %!>% 
    proper_filter(speed < val) 
}

val2 <- 10
fn_w_magrittr4(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 in a general way within magrittr itself without sacrificing important features of magrittr although for the problem here magrittr's eager lexical pipe is sufficient.

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.

G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341