There is plenty of questions on this topic on SO, including this question on how to evaluate expressions, this one showing some disagreement about using eval
, and this answer showing the eval(substitute(.))
trick.
A good reference to learn about non-standard evaluation (NSE) is the metaprogramming section of Advanced R, by Hadley Wickham.
I note a lot of disagreement in the answers, with some signalling the problems of using strings as something to be evaluated.
I am in this condition of using a character string as an argument for a child function.
Here is a reprex:
# Consider a little toy function:
little_fun <- function(z = 2, y = 1) {
(z + 2) / y
}
little_fun()
#> [1] 4
# I can call little_fun() on lists of arguments:
z_list <- c(1,2,3)
purrr::map_dbl(.x = z_list, ~ little_fun(z = (.x))) # This is basically a tidyverse equivalent for lapply()
#> [1] 3 4 5
# or also:
z_list <- c(1,2,3)
y_list <- c(-1, 0, 1)
purrr::map2_dbl(.x = z_list, .y = y_list, ~ little_fun(z = (.x), y = (.y))) # again, similar to mapply()
#> [1] -3 Inf 5
# But I also want to assign the parameters from a more general parent function:
big_fun <- function(par = "y") {
stopifnot(par %in% c("z", "y"))
par_list <- c(1,2,3)
purrr::map_dbl(.x = par_list, ~ little_fun(par = (.x))) # <--- key line <---
}
big_fun()
#> Error in little_fun(par = (.x)): unused argument (par = (.x))
My problem: I am still unable to get my code running.
My question: why is it bad to use characters as function arguments? Should I avoid this? and how? I would like to understand how to improve my reasoning and learn available alternatives.
I don't disagree with chinsoon12's answer to your question of "Should I avoid this?"
"protect yourself against future stupidity" – chinsoon12
Having said that, dynamically-assigned parameter names is one place where do.call
comes in handy.
big_fun <- function(par = "y") {
stopifnot(par %in% c("z", "y"))
par_list <- c(1,2,3)
purrr::map_dbl(.x = par_list, ~do.call(little_fun, as.list(setNames(.x, par))))
}
big_fun()
# [1] 4.000000 2.000000 1.333333
big_fun("z")
# [1] 3 4 5
I personally find programming like this at times mind-warping and problematic, and at others perhaps the only way to solve some problem elegantly. Sometimes it is the most terse, robust, and efficient solution available.
And just to check, I believe big_fun()
by itself should match this:
sapply(1:3, little_fun, z=2)
# [1] 4.000000 2.000000 1.333333