Manuel R
Manuel R

Reputation: 4145

Evaluate a value in R given either the name of a promise or the formal argument

Code:

args <- function() {
  list(
    x = "How old",
    y = "is",
    z = "the car?")
}

fun1 <- function(x = "How old", y = "is", z = "the car") {
  # collect arguments
  args_used   <- as.list(match.call()[-1])

  # Call fun2() using the args supplied by the user
  do.call(fun2, args_used)

}

fun2 <- function(x = args()$x, y = args()$y, z = args()$z) {
  paste(x, y, z)
}

Problem

Using fun1() and/or fun2() works if used directely, try e.g.,

> fun1("Where", "are", z = "the cars")
[1] "Where are the cars"

However, if I do:

list1 <- list(l1 = "Where", l2 = "Is this")
list2 <- list(l1 = "is", l2 = "the")
list3 <- list(l1 = "Peter", l2 = "new iPhone?")

mapply(function(i, j, k) fun1(x = i, y = j, z = k),
       i = list1,
       j = list2,
       k = list3,
       SIMPLIFY = FALSE)

I get

Error in paste(x, y, z) : object 'i' not found

I know why the problem happens and I even know how to fix it using eval(parse(text = "")) (although I am aware of potential problems of the eval-parse strategy (see, e.g.: this discussion)

Therefore rewriting fun1() like this:

fun1 <- function(x = "How old", y = "is", z = "the car") {
  # collect arguments
  args_used <- as.list(match.call()[-1])
  args_used <- lapply(names(args_used), function(w) eval(parse(text = w))) 

  # Call fun2() using the args supplied by the user
  do.call(fun2, args_used)

}

and subsequently using mapply(....) as above works.

However here it becomes tricky.

  1. Notice what happens if I change w in lapply(names(args_used), function(w) eval(parse(text = w))) to x (the same holds true for y and z. Now calling mapply as above again, give:
    $`l1`
    [1] "x is Peter"
    
    $l2
    [1] "x the new iPhone?"
    

    Clearly not what I wanted. Again I understand why this happens and a fix would be to not use x but something like w but any name a pick is from now on reserved. Not ideal.

  2. The approach also breaks if one of the arguments is called via the ... argument. Modifying fun1() like this:

    fun1 <- function(x = "How old", y = "is", ...) {
      # collect arguments
      args_used <- as.list(match.call()[-1])
      args_used <- lapply(names(args_used), function(x) eval(parse(text = x))) 
    
      # Call fun2() using the args supplied by the user
      do.call(fun2, args_used)
    
    }
    

    now results in

    Error in eval(parse(text = x)) : object 'z' not found

Again, I understand why (because the name "z" is unknown within the fun1() environement) but the question now is:

How do I solve this issue in a way that it avoids problem 1 and problem 2?

Upvotes: 0

Views: 111

Answers (1)

MrFlick
MrFlick

Reputation: 206197

Just have your function evaluate the parameters in the calling environment. This seems to work (note the addition of envir=parent.frame()).

fun1 <- function(x = "How old", y = "is", z = "the car") {
  args_used  <- as.list(match.call()[-1])
  do.call(fun2, args_used, envir=parent.frame()) 
}

But I can't see why you are using lazy evaluation at all here. You can just capture all the arugments as part of the environment and pass along their values

fun1 <- function(x = "How old", y = "is", z = "the car") {
  args_used  <- as.list(environment())
  # if you need ...: args_used <- c(as.list(environment()), list(...))
  do.call(fun2, args_used) 
}

Note that the environment() will capture all variables that exist at the time you call it. So if you just want the variables that are passed in to your function, be sure to call it at the start of your function.

If your parameters have leading dots in their names, R normally treats these as "hidden" so you'll need to set the all.names=TRUE parameter to the as.list.environment function to get them. You can do

args_used  <- as.list(environment(), all.names=TRUE)

Upvotes: 1

Related Questions