You can use purrr::reduce
(or just Reduce
) to assemble the pieces, and rlang to munge the ingredients. I made one change to opts
, storing the functions as expressions instead of raw functions, as otherwise the name of lag
isn't stored anywhere, so there is no way to know which parameters go with which function. You could use quosures instead of expressions, but since the data is determined by the pipeline structure, not any references, it is easier to use expressions so you can ignore environments.
Assembling the pipeline is not too bad; it's just reduce
ing the calls, with .init
set, splicing each call into the resulting expression. Adding the parameters is a little harder, but the heavy lifting can be done with rlang::call_modify
. The parameters have to be subset out of opts$args
, which means altering the input expr into a string with which to subset. This can be done with expr_name(.y[[1]])
, where the [[1]]
is to drop the parentheses from the call. The parameters thus subset need to be unquote-spliced into call_modify
so they are passed raw, not as a list.
The resulting expression of a pipeline can be evaluated with purrr::eval_tidy
, or because it an ordinary expression, plain old eval
.
library(purrr)
library(rlang)
opts = list(x = 1:10,
chain = list(expr(cumsum()), expr(diff())),
args = list(diff = list(lag = 2)))
reduce(opts$chain, ~expr(!!.x %>% !!.y), .init = opts$x)
#> 1:10 %>% cumsum() %>% diff()
chain <- reduce(opts$chain,
~expr(!!.x %>% !!call_modify(.y, !!!opts$args[[expr_name(.y[[1]])]])),
.init = opts$x)
chain
#> 1:10 %>% cumsum() %>% diff(lag = 2)
eval(chain) # or eval_tidy(chain)
#> [1] 5 7 9 11 13 15 17 19
If you'd rather store the data as symbols instead of expressions of calls (i.e. without the parentheses), you can drop the call subsetting in the expr_name
call, but will need to call call2
on the symbol to turn it into a call (i.e. add parentheses). call2
can be used to add parameters instead of call_modify
, too:
opts = list(x = 1:10,
chain = list(expr(cumsum), expr(diff)),
args = list(diff = list(lag = 2)))
reduce(opts$chain, ~expr(!!.x %>% !!.y), .init = opts$x)
#> 1:10 %>% cumsum %>% diff
reduce(opts$chain, ~expr(!!.x %>% !!call2(.y)), .init = opts$x)
#> 1:10 %>% cumsum() %>% diff()
chain <- reduce(opts$chain,
~expr(!!.x %>% !!call2(.y, !!!opts$args[[expr_name(.y)]])),
.init = opts$x)
chain
#> 1:10 %>% cumsum() %>% diff(lag = 2)
eval(chain) # or eval_tidy(chain)
#> [1] 5 7 9 11 13 15 17 19
call2
will accept a variety of inputs to specify the function, so the above will actually work fine if opts$chain
is just a character vector of function names c("cumsum", "diff")
without any modification to the code (though expr_name
would be superfluous). If there were a way to figure out which args
to get, it would work on the original data, too (though the intermediary code would look a bit uglier).