Skip to content

Expand on variable scope in documentation #63

@schloerke

Description

@schloerke

Wrote this up for future_promise() usage in #62 . It did not make sense for the vignette. So I added into an issue for the time being.

## Variable scope in `future_promise()` and `future::future()`

`future_promise()` is a promise **first** and executes using `future::future()` **second**. When using `future_promise()`, you should use the same precautions that you would use with a regular `promises::promise()`.

When `future` blocks on the main R session, this prevents values from being changed before they are submitted to the external worker.  While a `promise` waits to be executed, it is known that variables that have **not been `forced()`** or properly **scoped** can change from their expected values before evaluation occurs. This can also occur with properly scoped environment values as only the _pointer_ to the environment is static. This allows for the values within the environment to be altered before the `promise` is executed, which is likely undesirable.


#### Scope

In the example below, the variable `i` is not forced to a specific value for the promise. With the promise resolving within the global environment, the latest `i` value will be used for all of the promises waiting to resolve.

```r
items <- list()
for (i in 1:10) {
  items <- c(items, list(
    promise_resolve(TRUE) %...>% {i}
  ))
}
promise_all(.list = items) %...>%
  { print(unlist(.)) }
# #> [1] 10 10 10 10 10 10 10 10 10 10
```

To combat variable scoping issues, functions can be used to create local environments that will _scope_ the expected `i` value.

```r
lapply(1:10, function(i) {
  promise_resolve(TRUE) %...>% {i}
}) %>%
  promise_all(.list = .) %...>%
  { print(unlist(.)) }
#> [1]  1  2  3  4  5  6  7  8  9 10
```


#### Changing environments

Environments can have their values changed after the original promise creation.  This can cause unexpected behavior when evaluating a promise.

For example, the environment `env` below will have its value `a` changed from `1` to `2` before the promise is resolved.  This causes the unexpected value of `2` to be returned in the promise.
```r
{
  env <- new.env()
  env$a <- 1

  promise_resolve(TRUE) %...>%
    { Sys.sleep(1); env$a } %...>%
    { print(.) }
  env$a <- 2
  print("Changed env$a")
}
#> [1] "Changed env$a"
#> [1] 2
```

To address lazy evaluation and environment variable issues, we can store the values to a local variable and force their evaluation.

Fixing the example, we can capture the value `a` (or turn the environment into a `list()`).
```r
{
  env <- new.env()
  env$a <- 1

  a_value <- force(env$a)
  promise_resolve(TRUE) %...>%
    { Sys.sleep(1); a_value } %...>%
    { print(.) }
  env$a <- 2
  print("done")
}
#> [1] "done"
#> [1] 1
```

The expression in `future_promise(expr)` will behave like a promise and should have its volitile variables scoped and `force()`ed to achieve similar evaluations when using `future::future()` directly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions