andrewH
andrewH

Reputation: 2321

R: Scoping, timing, and <<-

In the code below, I expect both f and the final a to return 3. But in fact they both return 2. Why is this? Hasn't 3 replaced 2 in the enclosing environment at the time the promise is evaluated?

a <- 1

f <- function(a){
  a <<- 3
  cat(a)
}

f(a <- 2)
a

Note that If I use an = instead of a <- in the call to f, the final a is 3 as expected, but f remains 2.

Upvotes: 0

Views: 43

Answers (1)

MrFlick
MrFlick

Reputation: 206197

Let's walk through the code

a <- 1 

assigns the value 1 to the name a in the global environment.

f <- function(a){...}

creates a function saved to the name f in the global environment.

f(a <- 2)

Now we are calling the function f with the expression a<-2 as a parameter. This expression is not evaluated immediately. It is passed as a promise. The global value of a remains 1.

Now we enter the body of the function f. The expression we've passed in is assigned to the local variable a in the function scope (still un-evaluated) and a in the global remains 1. The fact they they both involve the symbol a is irrelevant. There is no direct connection between the two a variables here.

a <<- 3

This assigns the value of 3 to a in a parent scope via <<- rather than the local scope as <- would do. This means that the a refered to here is not the local a that now hold the parameter passed to the function. So this changes the value of a in the global scope to 3. And finally

cat(a)

Now we are finally using the value that was passed to the function since the a here refers to the a in the local function scope. This triggers the promise a <- 2 to be run in the calling scope (which happens to be the global scope). Thus the global value of a is set to 2. This assignment expression returns the right-hand-side value so "2" is displayed from cat().

The function exits and

a

shows the value of the a in the global environment which is now a. It was only the value 3 in the brief moment between the two expressions in f.

If you where to call

f( a=2 )

This is very different. Now we are not passing an expression to the function anymore, we are passing the value 2 to the named function parameter a. If you tried f(x=2) you would get an error that the function doesn't recognize the parameter named "x". There is no fancy lazy expression/promise evaluation in this scenario since 2 is a constant. This would leave the global value set to 3 after the function call. f(a <- 2) and f(a = a <- 2) would behave the same way.

Upvotes: 2

Related Questions