Konrad Rudolph
Konrad Rudolph

Reputation: 545528

match.call called in wrong environment when eval’ing

I tried implementing a function let with the following semantics:

> let(x = 1, y = 2, x + y)
[1] 3

… which is conceptually somewhat similar to substitute with the syntax of with.

The following code almost works (the above invocation for instance works):

let <- function (...) {
    args <- match.call(expand.dots = FALSE)$`...`
    expr <- args[[length(args)]]
    eval(expr,
         list2env(lapply(args[-length(args)], eval), parent = parent.frame()))
}

Note the nested eval, the outer to evaluate the actual expression and the inner to evaluate the arguments.

Unfortunately, the latter evaluation happens in the wrong context. This becomes apparent when trying to call let with a function that examines the current frame, such as match.call:

> (function () let(x = match.call(), x))()
Error in match.call() :
  unable to find a closure from within which 'match.call' was called

I thought of supplying the parent frame as the evaluating environment for eval, but that doesn’t work:

let <- function (...) {
    args <- match.call(expand.dots = FALSE)$`...`
    expr <- args[[length(args)]]
    parent <- parent.frame()
    eval(expr,
         list2env(lapply(args[-length(args)], function(x) eval(x, parent)),
                  parent = parent)
}

This yields the same error. Which leads me to the question: how exactly is match.call evaluated? Why doesn’t this work? And, how do I make this work?

Upvotes: 9

Views: 1436

Answers (1)

flodel
flodel

Reputation: 89057

Will this rewrite solve your problem?

let <- function (expr, ...) {
    expr  <- match.call(expand.dots = FALSE)$expr
    given <- list(...)
    eval(expr, list2env(given, parent = parent.frame()))
}

let(x = 1, y = 2, x + y)
# [1] 3

Upvotes: 6

Related Questions