Search code examples
rpromiselazy-evaluation

Lazy evaluation and promise data structure


I have been grappling with the application of promise since I first read about it on Advanced R. It is mentioned that a promise is a data structure that powers lazy evaluation. The concept of lazy evaluation is quite clear as function arguments are only evaluated whenever they are accessed. However, in some examples I just cannot discover the presence of a promise and how/where it is evaluated. Consider the following example from Advanced R:

y <- 10
h02 <- function(x) {
  y <- 100
  x + 1
}

h02(y)
[1] 11

It returns 11 instead of 101, as apparently when we assign a variable like y which already exists in the global environment to x it is bound and evaluated outside of the function. So I would like to know is a promise always involved some sort of assignment or every expression could be a promise and how we can detect their presence. It is mentioned that they are evaluated in the calling environment of a function. So the second question is are their evaluation environments different from normal arguments as user-defined arguments are evaluated outside of the function.

There is also another example which I cannot understand why it involves lazy evaluation, and we only see Calculating... once.

double <- function(x) {
message("Calculating...")
x * 2
}
h03 <- function(x) {
c(x, x)
}
h03(double(20))
Calculating...
[1] 40 40

I am so sorry if I sound a little bit confused here, I got the point but it has never quite sunk in and I wanted to ask for a little bit of explanation for which I am very grateful.

Thank you very much in advance


Solution

  • When an object such as y within h02 is created in a function it is created in the local execution frame/environment of that function (a new frame is created each time the function is run). The created object is distinct from an object of the same name in any other environment.

    Regarding h03 once a promise is forced, i.e. evaluated, its value is stored in the promise's value component and its evaled component is set to TRUE so that upon further accesses it does not have to be evaluated again.

    Arguments of functions are promises but normally not other objects. Use pryr to inspect objects.

    library(pryr)
    
    f <- function(x) { 
      z <- 1
    
      cat("is_promise(z):", is_promise(z), "\n")
    
      cat("is_promise(x):", is_promise(x), "\n")
      cat("before forcing - promise_info(x):\n")
      print(promise_info(x))
    
      force(x)
      cat("after forcing - promise_info(x):\n")
      print(promise_info(x))
    
      delayedAssign("w", 3)
      cat("is_promise(w):", is_promise(w), "\n")
    
      invisible()
    }
    a <- 3
    f(a)
    

    giving:

    is_promise(z): FALSE 
    is_promise(x): TRUE 
    before forcing - promise_info(x):
    $code
    a
    
    $env
    <environment: R_GlobalEnv>
    
    $evaled
    [1] FALSE
    
    $value
    NULL
    
    after forcing - promise_info(x):
    $code
    a
    
    $env
    NULL
    
    $evaled
    [1] TRUE
    
    $value
    [1] 3
    
    is_promise(w): TRUE