bers
bers

Reputation: 5771

Why does match.call not work when the original call is wrapped in a function?

I want to pass down function arguments to recursively call a function within itself (usually with a break condition, of course).

I learnt that match.call should work to capture all arguments, and it works - until I wrap the original call in another function.

inner <- function(my_arg) {
    message(my_arg)
    do.call("inner", as.list(match.call()[-1]))
}

# this yields an error ... (unexpected)
outer <- function() {
    mydata <- data.frame(1)
    inner(mydata)
}
outer()

# ... while this yields an infinite loop (expected)
mydata <- data.frame(1)
inner(mydata)

This outputs:

1
Error in is.data.frame(my_arg) : object 'mydata' not found

Why is that? Is this intended? How can I fix this?

Upvotes: 0

Views: 342

Answers (2)

Roland
Roland

Reputation: 132696

It's really difficult to explain the error because it results from the interaction of do.call, match.call and recursion. The problem results from when the promises of the nested calls inner(my_arg = mydata) are forced. When message is called, R searches the function scope and, in case the object is not found, the enclosing environments. This appears to fail when a promise in the nested calls hasn't been forced (due to your do.call("inner", as.list(match.call()[-1])) construct).

> traceback()
5: message(my_arg) at #2
4: inner(my_arg = mydata)
3: do.call("inner", as.list(match.call()[-1])) at #4
2: inner(mydata) at #4
1: outer()

I suggest you study the language definition, e.g. Section 4.3.3.

Also, why do you need match.call here? Just use inner(my_arg) instead of that do.call with match.call construct. That immediately forces the promise and everything works fine.

Upvotes: 1

Allan Cameron
Allan Cameron

Reputation: 173793

This happens because of scoping. Hopefully this modification of your two functions will give a clear picture of what's going on (with no infinite loops!), and how to fix it.

inner <- function(my_arg)
{
  mc <- match.call()
  cat("Call to inner:\n")
  print(mc)
  cat("\nSymbol to be evaluated within \"inner\":\n")
  print(as.list(mc)$my_arg)
  cat("\nSymbol evaluated in scope of \"inner\":\n")
  tryCatch(print(eval(as.list(mc)$my_arg)),
           error = function(e) cat("**Error** - symbol not found\n"))
  cat("\nSymbol evaluated in parent frame of \"inner\":\n")
  tryCatch(print(eval(as.list(mc)$my_arg, envir = parent.frame())),
           error = function(e) cat("**Error** - symbol not found\n"))
}

outer <- function()
{
  my_data <- "outer test string"
  inner(my_data)
}

Which we can test as follows:

inner("inner test string")
#> Call to inner:
#> inner(my_arg = "inner test string")
#> 
#> Symbol to be evaluated within "inner":
#> [1] "inner test string"
#> 
#> Symbol evaluated in scope of "inner":
#> [1] "inner test string"
#> 
#> Symbol evaluated in parent frame of "inner":
#> [1] "inner test string"

outer()
#> Call to inner:
#> inner(my_arg = my_data)
#> 
#> Symbol to be evaluated within "inner":
#> my_data
#> 
#> Symbol evaluated in scope of "inner":
#> **Error** - symbol not found
#> 
#> Symbol evaluated in parent frame of "inner":
#> [1] "outer test string"

Upvotes: 1

Related Questions