Help with writing a custom pipe and environments

I think the problem is that .x and .f in your pipe function live in different environments. Below is a workaround that you probably won't find helpful, since I'm just reassigning your function in .f, its name will be lost through this. But this shows that if we bring .f and .x in the same environment it works. I tried playing around making f_env child or parent environment of the execution environment of %>=%, but everything I tried crashed R. If we only use f_env in eval() then this time .x won't be found.

In the example below I use the magrittr pipe instead of |> because I'm not on 4.0 yet.

Ideally, we would find a way to solve this problem by rewriting the functions so that they use less string-to-language manipulation and instead work with calls and expressions. I gave it some thought in the last chunk, but I'm not sure how this attempt would fit together with your list containing args and the function name.

library(rlang)
library(testthat)
library(magrittr)

`%>=%` <- function(.x, .f, ...) {
  
  f_quo <- rlang::enquo(.f)
  f_exp <- rlang::quo_get_expr(f_quo)
  f_env <- rlang::quo_get_env(f_quo)
  
  parsed <- parse_function(deparse1(f_exp))
  .f <- get(x = parsed$func, envir = f_env, mode = "function")
  
  cmd <- make_command(parsed)
  eval(parse(text = cmd))
  
}

parse_function <- function(.f_string){
  
  func <- gsub("\\(.*$", "", .f_string)
  args <- stringr::str_extract(.f_string, "\\(.*")
  args <- gsub("^\\(", "", args)
  args <- gsub("\\)$", "", args)
  args <- ifelse(args != "", paste0(args, ", "), "")
  
  list("func" = func,
       "args" = args)
}

make_command <- function(parsed_function){
  
  paste0(".x %>% ",
         ".f",
         "(",
         parsed_function$args, ")"
  )
  
}



  
a <- c(seq(1:10), NA)
  
my_log <- log
my_mean <- mean
  
a %>=%
  my_mean(na.rm = TRUE) %>=%
  my_log()
#> [1] 1.704748
  
log(mean(a, na.rm = TRUE))
#> [1] 1.704748


test_that("test equality", {
  
  my_sqrt <- sqrt
  my_exp  <- exp
  my_mean <- mean
  
  result_pipe <- 1:10 %>=%
    my_sqrt() %>=%
    my_exp() %>=%
    my_mean()
  
  result <- my_mean(my_exp(my_sqrt(1:10)))
  
  expect_equal(result_pipe, result)
})
#> Test passed 😸

Created on 2022-04-03 by the reprex package (v0.3.0)

This is an attempt to rewrite %>=% without string manipulation, however I'm not sure what to do with the list containing the function name and its arguments. We can easily construct it though. Can this function solve your problem?

`%>=%` <- function(.x, .f, ...) {
  
  f_quo <- rlang::enquo(.f)
  f_exp <- rlang::quo_get_expr(f_quo)
  f_env <- rlang::quo_get_env(f_quo)
  f_chr <- deparse(f_exp[[1]])
  
  f <- get(f_chr, envir = f_env)
  q_ex_std <- rlang::call_match(call = f_exp, fn = f)
  expr_ls <- as.list(q_ex_std)
  
  # what should we do with this list ?!
  mylist <- list2("func" = f_chr,
                  "args" = expr_ls[-1])
  
  
eval(call2(f, .x, !!! expr_ls[-1]))

}