4 Compute on the language

R is very powerful with the ability of “Computing on the Language”(基于语言的计算) involving function, evaluation and environment.

4.1 core rules

  1. lazy evaluation to decide when to evaluate the parameter,
  2. call stack to decide where (start point) to evaluate the argument
  3. lexically scoping to where to find undefined symbol in the argument

4.2 lazy evaluation

f <- function(x) {y <- 1; message('in f()'); x;}

f(message('evaluate argument'))
#> in f()
#> evaluate argument

In f(), the parameter x is not evaluated until it’s used, i.e., after message('in f()').

f <- function(x) {message('in f()'); g(x)}
g <- function(x) {message('in g()'); x}

f(message('evaluate argument'))
#> in f()
#> in g()
#> evaluate argument

In f(), passing the parameter x as argument for g() doesn’t evaluate it.

4.3 call stack

f <- function(x) {message('in f()'); g(x)}
g <- function(x) {message('in g()'); y <- 2; x}

y <- 1
f(message('y = ', y))
#> in f()
#> in g()
#> y = 1

We already know that, message('y = ', y) is lazy evaluated after y <- 2 inside g(). However,when the evaluation happens, the stage is NOT the execution env of g(). Rather, we start from there, backtrack the call stack, until we arrive the start point, i.e., the caller env of f() (the global env here).

4.4 lexically scoping

g <- function(u) (u + v) / 2  
h <- function(u) {
    v <- 1  
    g(u)    
}    
v <- 2  
h(10)
#> [1] 6

Although g() is called inside h(), it finds v in where it’s defined, i.e., the global env.

Actually, lexically scoping is not a special rule, and the implemention is rather straightforward. The execution environment’s parent is function environment, i.e., when the function is created. So we just need to follow the general rule of parent environment.

4.5 technical details

  1. the evaluation of argument happens only once, refer to here
  2. call stack is implemented in that a frame’s parent is the previous frame, refer to here
  3. when you write a function factory, you may need to force evaluation