Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

R: Promise cannot find object

I know that is possible that you can write a function in R, where the default of an argument is a using another argument of the same function.

foo <- function(a, b = length(a)) {
  b
}

foo(a = c(1, 2))
[1] 2

However, as soon as I use the same argument in the actual function call I get an error:

foo(a = c(1, 2), b = length(a))
Error in foo(a = c(1, 2), b = length(a)) : object 'a' not found

I thought the promise of b = length(a) should be evaluated inside the function, where a is known, but apparently this is not happening. Can someone explain where the problem is and how I could make

foo(a = c(1, 2), b = length(a))

work?

like image 736
Gilean0709 Avatar asked Jul 05 '18 07:07

Gilean0709


2 Answers

When typing foo(a = c(1, 2), b = length(a)), a has to come from the calling environment, not the function environment. You'll need to use:

x <- 1:2
foo(a = x, b = length(x))

Or alternatively use a function argument:

foo <- function(a, fun = length) { fun(a) }

To quote Hadley:

More technically, an unevaluated argument is called a promise, or (less commonly) a thunk. A promise is made up of two parts:

  • The expression which gives rise to the delayed computation. (It can be accessed with substitute(). See non-standard evaluation for more details.)

  • The environment where the expression was created and where it should be evaluated.

You got it right that arguments are promises, and not evaluated until needed. But, the promise includes the environment where it was created, which in this case in the global environment.

like image 96
Axeman Avatar answered Nov 05 '22 09:11

Axeman


My understanding is that when you explicitly give arguments to a function, the default behaviour is to evaluate said arguments in the environment that they were given in, not the function execution environment (as @Axeman's answer details).

It's possible to get around this (whether you should is a different matter). You can use a combination of substitute() (to capture the unevaluated argument) and eval() to change the environment that the argument gets evaluated in. Here we explicitly evaluate b in foo's execution environment:

foo <- function(a, b = length(a)) {
  eval(substitute(b), env = environment())
}

(Due to defaults in eval(), it would be sufficient to just write eval(substitute(b)). But sometimes it's nice to be explicit; like this, it is much more obvious that we are changing the evaluation environment.)

Now the following won't throw an error:

foo(a = c(1, 2), b = length(a))
#> [1] 2

However, if you decide to go this route, you should be very explicit in documenting such a function that the b argument is evaluated in the execution environment. For example, what should happen when a is present both in the environment that the argument is given in and the execution environment? This can be unexpected behaviour, if not well documented (and a source of hard-to-diagnose bugs, even if well documented).

a <- 1:10
foo(a = c(1, 2), b = length(a))
#> [1] 2

For more details on evaluation (and pitfalls), you can check out the Evaluation chapter in Advanced R.

Created on 2018-07-05 by the reprex package (v0.2.0).

like image 3
Mikko Marttila Avatar answered Nov 05 '22 08:11

Mikko Marttila