rlang Expressions in List as Args to Function

137 views Asked by At

I'm trying to parse user input as arguments to a function call (within an expression). It seems like I'm close but !!! is wrapping my arguments in parenthesis which is not working. I'm trying to recreate the following with user inputs:

recipe(mpg ~ cyl + hp + wt + disp, data = mtcars) %>% 
  step_log(disp, base = 10, offset = 0) %>% 
  prep()
library(rlang)
library(tidymodels)

user_dv <- "mpg"
user_idv <- c("cyl", "hp", "wt", "disp")

user_step <- "log"
user_selector <- "disp"
user_args <- "base = 10, offset = 0"

formula <- as.formula(paste(user_dv, paste(user_idv, collapse = " + "), sep = " ~ "))

rcp <- expr(recipe(!!formula,data = mtcars))

add_step <- function(x, step, selector, args){
  f <- parse_expr(paste0("step_", step))
  vars <- ensym(user_selector)
  
  args <- args %>% 
    str_replace(",", ";") %>% 
    parse_exprs()

  step_expr <- call2(f, vars, !!!args)
  
  expr(!!x %>% !!step_expr)
  
}

rcp %>% 
  add_step(user_step, user_selector, user_args) %>% 
  eval() %>% 
  prep()

My expression ends up looking like this:

recipe(mpg ~ cyl + hp + wt + disp, data = mtcars) %>% 
      step_log(disp, (base = 10), (offset = 0))

Which does not prep()

1

There are 1 answers

1
Artem Sokolov On BEST ANSWER

After parse_exprs(), you end up with assignment expressions stored in an unnamed list:

# [[1]]
# base = 10
#
# [[2]]
# offset = 0

while, to get the effect you want with !!!, they need to be values in a named list:

# $base
# [1] 10
#
# $offset
# [1] 0

With that said, I suggest "forwarding the dots" instead, because it leads to a simpler and more flexible implementation:

add_step <- function(x, step, selector, ...){
  f <- parse_expr(paste0("step_", step))
  vars <- sym(selector)
  step_expr <- call2(f, vars, ...)      # <---- forwarding the dots here

  expr(!!x %>% !!step_expr)
}

The user can now simply provide the desired arguments directly to add_step():

rcp %>% add_step(user_step, user_selector, base=10, offset=0)

# recipe(mpg ~ cyl + hp + wt + disp, data = mtcars) %>% 
#    step_log(disp, base = 10, offset = 0)

OR store them in a list and use !!! on your function:

user_args <- list(base = 10, offset=0)
rcp %>% add_step(user_step, user_selector, !!!user_args)