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.

zx8754
  • 52,746
  • 12
  • 114
  • 209
000andy8484
  • 563
  • 3
  • 16
  • see https://r.789695.n4.nabble.com/R-eval-parse-text-vs-get-when-accessing-a-function-td815553.html and https://tolstoy.newcastle.edu.au/R/help/05/02/12204.html and https://stat.ethz.ch/pipermail/r-help/2007-January/123211.html – chinsoon12 Feb 14 '20 at 09:54
  • "protect yourself against future stupidity" – chinsoon12 Feb 14 '20 at 09:55

1 Answers1

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
r2evans
  • 141,215
  • 6
  • 77
  • 149
  • Shouldn't the output be same as `purrr::map_dbl(.x = z_list, ~ little_fun(z = (.x)))` here? – Ronak Shah Feb 14 '20 at 10:03
  • Not with a default of `par="y"`. Have you tried it with `big_fun("z")`? That matches, too. – r2evans Feb 14 '20 at 10:06
  • 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`). – r2evans Feb 14 '20 at 10:07
  • 1
    do.call()! Thank you very much for this, also for sharing these considerations. – 000andy8484 Feb 14 '20 at 12:10