Home > Software design >  Force-evaluate function argument in order to "trick" substitute()?
Force-evaluate function argument in order to "trick" substitute()?

Time:11-09

say I have a function from an R package which I want to wrap in a closure for convenience and better code readability. For simplicity, let's assume the function looks as follows:

fun <- function(text) {
    as.character(substitute(text))
}

When I call that function from the console, e.g.

fun(text = "bar")

the returned value is "bar", which in my case is the desired behavior. The reason the function is written the way it is, is that in case I call

fun(text = bar)

the output is also "bar". Just for context. I obviously didn't write this function by myself, I just want to use it.

The problem: When I call fun from within a function, like e.g.

fun2 <- function(foo) {
    fun(text = foo)
}
fun2(foo = "bar")

the output will always be "foo" instead of "bar", regardless of what I assign to foo in the call to fun2. Ofc I know this is how substitute() is intended to work, but this makes it impossible (or at least very nasty?), to programmatically work with the function fun.

My Question: Is there a way to achieve the desired behavior without rewriting fun?

Thanks a lot in advance :)

CodePudding user response:

You could construct the call inside fun2 and evaluate it:

fun2 <- function(foo) {
  eval(call("fun", foo))
}

fun2(foo = "bar")
#> [1] "bar"

The problem here is that it now no longer works if you pass bar unquoted:

fun2(foo = bar)
#> Error in eval(call("fun", foo)) : object 'bar' not found

So you would need something like:

fun2 <- function(foo) {
  if(exists(as.character(substitute(foo)), parent.frame())) {
    eval(call("fun", foo))
  } else {
    eval(call("fun", as.character(substitute(foo))))
  }
}

Which now works either way:

fun2(foo = "bar")
#> [1] "bar"

fun2(foo = bar)
#> [1] "bar"

The problem here is that if bar actually exists, then it will be interpreted as the value of bar, so we have:

fun2(foo = "bar")
#> [1] "bar"

fun2(foo = bar)
#> [1] "bar"

bar <- 1

fun2(foo = bar)
#> [1] "1"

Which is presumably not what you intended.

However, if you were going to do it this way, it probably no longer makes sense to call fun at all. Perhaps the easiest thing to do is

fun2 <- function(foo) {
  as.character(match.call()$foo)
}

fun2("bar")
#> [1] "bar"

fun2(bar)
#> [1] "bar"

bar <- 1

fun2(bar)
#> [1] "bar"
  •  Tags:  
  • r nse
  • Related