10

One of R's greatest feature is lazy evaluation. This leads to the often encountered style that one can use an arguments as the value of another arguments. For example, in Hadley's great book on Advanced R you see this example:

g <- function(a = 1, b = a * 2) {
  c(a, b)
}
g()
#> [1] 1 2
g(10)
#> [1] 10 20

Now, I would like to do the same for plot with xlim and ylim, however, it is not working:

> plot(1, 1, ylim = c(0,1), xlim = ylim)
Error in plot.default(1, 1, ylim = c(0, 1), xlim = ylim) : 
  object 'ylim' not found
> plot(1, 1, xlim = c(0,1), ylim = xlim)
Error in plot.default(1, 1, xlim = c(0, 1), ylim = xlim) : 
  object 'xlim' not found
  • Does anybody know why?
  • Is there a way to still achieve this?
Henrik
  • 14,202
  • 10
  • 68
  • 91
  • I think this is easy to explain with the updated definitions in http://adv-r.had.co.nz/Environments.html: default arguments are evaluated the execution environment of the function, supplied arguments are evaluated the calling environment of the function. – hadley May 24 '14 at 10:30

2 Answers2

13

Quoting from the good manual:

4.3.3 Argument evaluation

One of the most important things to know about the evaluation of arguments to a function is that supplied arguments and default arguments are treated differently. The supplied arguments to a function are evaluated in the evaluation frame of the calling function. The default arguments to a function are evaluated in the evaluation frame of the function.

To see what that means in practice, create a function in which one argument's default value is a function of another argument's value:

f <- function(x=4, y=x^2) {
    y
}

When called with y's default value, R looks to evaluate y in the evaluation frame of the function call, i.e. in the same environment in which the entire body of the function gets evaluated -- a place where x had very well better (and of course does) exist:

f() 
# [1] 16

When called with a supplied value of y, R looks in the evaluation frame of the calling function (here the global environment), finds no x, and lets you know so in its error message:

f(y=x^2)
# Error in f(y = x^2) : object 'x' not found
Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
  • That last line is quite interesting. So even though you've set the default `x` value to 4, R will not find it in the evaluation frame? Obviously "No", but it's a bit hard to understand why. – Rich Scriven May 22 '14 at 23:09
  • @RichardScriven -- Yeah, that's why I used that example :-) You won't feel comfortable with it until precisely that moment when you really completely grasp this! (For me, that moment came years after first reading that passage; hopefully this will help speed the process for some others!) – Josh O'Brien May 22 '14 at 23:11
  • ...which may not be far off because you've made me curious now. :) I also had no idea that default function arguments go into the global environment. I figured they stayed in the function environment and the function went into the global environment. I have reading to do! – Rich Scriven May 22 '14 at 23:13
  • Is there no way to force the evaluation of supplied arguments within the frame of the function call? E.g., with one of the usual suspects: `eval`, `substitute`, ... – Henrik May 23 '14 at 08:50
  • Nope, there's no way. You can set a function up to do non-standard evaluation of supplied arguments, though. Think, for example, of the functions that take formulas as arguments, or have a look at what the `curve` function does with its initial `expr` argument. – Josh O'Brien May 23 '14 at 08:58
  • @JoshO'Brien It's pretty simple with NSE: `eval(substitute(xlim))`. – hadley May 24 '14 at 10:32
  • @hadley But that won't help you in a call to a pre-existing function that's not already set up to perform the NSE, which is what the OP asked about. (I agree it's simple to set up the NSE when *defining* a function, which is why I noted that in the comment just preceding yours ;=).) – Josh O'Brien May 24 '14 at 19:17
  • @JoshO'Brien oh I see what you mean now – hadley May 24 '14 at 23:10
5

There is a scoping issue here. In the statement plot(1, 1, ylim = c(0,1), xlim = ylim), the name ylim is only available as the name of a parameter and is not available to the caller in general.

For a variable to be used on the right hand side of the assignment, it must be available in the calling scope.

The reason your first example works is because you are writing default arguments into your function definition, which does have access to all the parameters.

One possible workaround, which only makes sense if this is the same default you want in a lot of cases, is to wrap the plot function in a new function that has this behavior.

myplot <- function(x, y, ylim, xlim = ylim,...) {
  plot(x,y, ylim = ylim, xlim = xlim,...)
}

myplot(1,1, ylim=c(0,1))
merlin2011
  • 71,677
  • 44
  • 195
  • 329
  • But why is it working in the example above (sorry, the first example was wrong)? – Henrik May 22 '14 at 22:48
  • @Henrik, I just updated. The second scenario is a function **call** which is scoped to the caller's context. The first scenario is a function **definition** which is scoped to the callee's context. – merlin2011 May 22 '14 at 22:49
  • @Henrik, I also updated with a somewhat weak workaround. – merlin2011 May 22 '14 at 22:55