2

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.

2

1 Answer 1

3

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
Sign up to request clarification or add additional context in comments.

4 Comments

Shouldn't the output be same as purrr::map_dbl(.x = z_list, ~ little_fun(z = (.x))) here?
Not with a default of par="y". Have you tried it with big_fun("z")? That matches, too.
Said differently, big_fun() is the same as big_fun("y"), which should not match the previous example that used little_fun(z = (.x)) (emphases on the z).
do.call()! Thank you very much for this, also for sharing these considerations.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.