Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove init_chunks in docs #31

Merged
merged 6 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 106 additions & 141 deletions vignettes/basic_chunks.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -11,82 +11,72 @@ vignette: >

# The chunks container

The main concept behind the code chunks is the chunks container. This container consists of two elements:
The main concept behind code reproducibility in teal is the chunks container object. This object consists of two elements:

1. A stack of quoted R expressions, each called a "chunk"
2. An environment carrying variable values
2. An environment carrying variables and their values

<img src="images/container.png" alt="container" style="width: 100%;"/>

Each chunk can be evaluated inside the current environment. To evaluate all chunks inside the same environment, the `chunks` R6 object was created in `teal.code`. We refer to this object as "chunks container". It internally has a stack of `chunk` objects and the environment where they will get evaluated.
The chunks container object allows the evaluation of all expressions it contains in its own isolated environment, thus having no side effects on the surrounding environment.

The next sections will explain what a chunk is and how it is evaluated.

# What is a chunk?

A quoted R expression is a necessary step to create a **chunk** object, which is an R6 object of class `chunk_call`. Quoted R expressions can be created in three different ways:
A quoted R expression is a necessary step to create a **chunk** object, which is an R6 object of class `chunk_call`. Quoted R expressions can be created in many different ways, four of which will be described:
```{r}
a <- 3

# Creating a chunk by quote ------------------------------
expr_a <- quote(sum(a, a))
print(expr_a)
print(class(expr_a))


# Creating a chunk by bquote ------------------------------
expr_b <- bquote(b <- sum(a, a))
# Creating a chunk by substitute ------------------------------
expr_b <- substitute(b <- sum(a, a))
print(expr_b)
print(class(expr_b))

# Creating a chunk by call -------------------------------
expr_c <- call("sum", a, a)
print(expr_c)
print(class(expr_c))

# Creating a chunk by rlang::expr -------------------------------
expr_d <- rlang::expr(sum(a, a))
print(expr_d)
```

To evaluate the expressions of class `call` or an assignment given by class `<-` above, R uses the `eval` function. This function evaluates each single `call` inside the current environment, in case no other environment is given. In the example code you can see what happens upon evaluating the expressions:
To evaluate the expressions of class `call` or an assignment given by class `<-` above, R uses the `eval` function. This function evaluates each single `call` inside the current environment by default, but it does contain a parameter to input a specific environment argument to execute the expression in.

```{r}
a <- 3
expr_a <- quote(sum(a, a))
expr_b <- bquote(b <- a + a)
expr_c <- call("sum", a, a)
expr_b <- substitute(b <- a + a)

eval(expr_a)
eval(expr_b)
print(b)
eval(expr_c)
```

Now `chunk` objects can be created and evaluated using the expressions above as follows:
```{r}
a <- 3
expr_a <- quote(sum(a, a))
expr_b <- bquote(b <- a + a)
expr_c <- call("sum", a, a)
`chunk` objects can be created and evaluated using the expressions above as follows:

```{r}
chunk_1 <- teal.code::chunk$new(expression = expr_a)
chunk_1$eval()

chunk_2 <- teal.code::chunk$new(expression = expr_b)
chunk_2$eval()
print(b)

chunk_3 <- teal.code::chunk$new(expression = expr_c)
chunk_3$eval()
```

Note that `teal.code::chunk` is merely an alias for `teal.code::chunk_call`. And so the following code is the same as the above code:

```{r}
chunk_1 <- teal.code::chunk_call$new(expression = expr_a)
chunk_1$eval()

chunk_2 <- teal.code::chunk_call$new(expression = expr_b)
chunk_2$eval()
print(b)

chunk_3 <- teal.code::chunk_call$new(expression = expr_c)
chunk_3$eval()
```

## Motivation for the chunk (chunk_call) object
Expand All @@ -107,177 +97,152 @@ chunk_err$eval()
chunk_err$get_errors()
```

Internally, the `chunks` container will convert quoted R expressions into `chunk` objects as they are pushed in.
Internally, the `chunks` container will convert quoted base R expressions into `chunk` objects as they are pushed in.

## Creation of a chunks container object

The next sections will tell in a step by step guide which features are provided by the `chunks` container object.
A chunks container may be initialized as an empty container.

# Step by step to understand chunks container
```{r}
# initializing code chunks -------------------------------------------
chunks_container_empty <- teal.code::chunks$new()
```

## General information
However, it can also be initialized with a specific environment.

Normally as a module developer the chunks container will be used within the `server` function of a shiny/teal module. This enables storing the chunks container inside the shiny session. For simplicity reasons this feature will be used in the tutorial. To store a container inside the shiny session simply use the call `init_chunks()` and the `chunks` R6 object will be stored in `session$userData$<MODULENAME>$chunks`. After using `init_chunks()`, the functions dealing with chunks container can be used. Those are recommended. If for any reasons you want to use your own chunks container, it is possible. Please see the last section of this article for more information. So for now, we just call `teal.code::init_chunks()`.
```{r}
# initializing code chunks -------------------------------------------
env <- new.env()
env$var_to_be_erased <- "some_value"
env$x <- 0
chunks_container <- teal.code::chunks$new(envir = env)

As a simulation of the teal environment the `init_session` function is provided:
# method to list all variables in the chunks environment
chunks_container$ls()

```{r}
# pseudo code simulating a shiny session ----------------------------
init_session <- function() {
session <- new.env()
session$userData <- new.env() # nolint
session$ns <- function(x) paste0("x-", x)
return(session)
}
# function to add a chunk to a chunks container object
teal.code::chunks_push(quote(print(x)), chunks = chunks_container)

# initializing code chunks -------------------------------------------
session <- init_session()
teal.code::init_chunks(session = session)
# function to get all expressions from a chunks container code stack
teal.code::chunks_get_rcode(chunks_container)
```

## Feature 1: Reset (initialize the environment)
<img src="images/initialize_env.png" alt="reset" style="width: 100%;"/>

Normally reproducible code will be used inside a `renderPlot` or `renderTable` call of a shiny module. For simplicity reasons we just use the *pseudo* shiny session defined above in this tutorial. As a first step the chunks container should be handed over an analysis dataset (`anl`) and two variables `x = "abc"`, `y = 5`. Therefore you need to use the `teal.code::chunks_reset` function. It not only empties all current chunks inside the container, but also hands over all variables from the current environment to the container environment.
At any point, a chunks container can be reset. Resetting means that all expressions in its code stack will be emptied and its environment will be overridden by the inputted environment, which defaults to the parent environment.

To check that it worked as expected the function `teal.code::chunks_get_var` will be used and check that the values inside the chunks container are equal to the values from the environment.
```{r}
env <- new.env()
env$anl <- data.frame(left = c(1, 2, 3), right = c(4, 5, 6))
env$x <- "abc"
env$y <- 5

You can use this code snippet:
teal.code::chunks_reset(envir = env, chunks = chunks_container)

```{r}
# Adding variables to the chunks container -------------------------------------
anl <- data.frame(left = c(1, 2, 3), right = c(4, 5, 6))
x <- "abc"
y <- 5
# note that the variable var_to_be_erased is removed
chunks_container$ls()

teal.code::chunks_reset()
# this function is used to extract values of variables in a chunks container environment
# note that the variable x is overriden
teal.code::chunks_get_var("x", chunks = chunks_container)

# Double check variables were handed over-----------------------------
all.equal(x, teal.code::chunks_get_var("x"))
# note that the code stack has been emptied
teal.code::chunks_get_rcode(chunks_container)
```

<img src="images/reset.png" alt="reset" style="width: 100%;"/>


## Feature 2: Push - adding code snippets

To populate the chunks container, a chunk can be added using the `teal.code::chunks_push` function. Here two code snippets will be added:
As mentioned above, the `teal.code::chunks_push` function is used to push expressions into the chunks container.

```{r}
teal.code::chunks_push(bquote(y <- y + 1))
teal.code::chunks_push(bquote(x <- paste0(x, y)))
teal.code::chunks_push(substitute(y <- y + 1), chunks = chunks_container)
teal.code::chunks_push(substitute(x <- paste0(x, y)), chunks = chunks_container)
```

<img src="images/push.png" alt="push" style="width: 100%;"/>

## Feature 3: Get R code - showing the chunks container code

To reproduce what was done inside the chunks container, it is necessary to render the R code inside them. Therefore the chunks container can display all its code by calling `teal.code::chunks_get_rcode`. You can run this example to see the code:
As mentioned above, the `teal.code::chunks_get_rcode` function is used to get all expressions pushed into the chunks container.

```{r}
teal.code::chunks_get_rcode()
teal.code::chunks_get_rcode(chunks_container)
```
<img src="images/get_rcode.png" alt="get_rcode" style="width: 100%;"/>

## Feature 4: `eval` - evaluating the code

The `eval` function is responsible to run the code inside the chunks container. The `eval` function of a chunks container is called `teal.code::chunks_safe_eval`. It evaluates all chunks inside the container in the order they were pushed. It is not possible to change the order or run just pieces of the code.
## Executing the code stored in the stack

The `teal.code::chunks_safe_eval` will always return the value of the last evaluation. By `teal.code::chunks_get_var` it is possible to retrieve specific variables after evaluation.
The chunks container also has an `eval` method which runs all code inside the chunks container. This method is wrapped inside the function `teal.code::chunks_safe_eval`. It evaluates all chunks inside the container in the order they were pushed and returns the value of the last evaluated expression. It is not possible to change the order or run just some of the expressions.

```{r}
teal.code::chunks_safe_eval()
teal.code::chunks_get_var("x")
teal.code::chunks_get_var("y")
teal.code::chunks_safe_eval(chunks_container)
teal.code::chunks_get_var("x", chunks = chunks_container)
teal.code::chunks_get_var("y", chunks = chunks_container)
```

<img src="images/eval.png" alt="eval" style="width: 100%;"/>

## Feature 5: Is ok - check for errors and warnings

A chunks container object also has its own `eval` method. The function `teal.code::chunks_safe_eval` is merely a wrapper of this method. It is named `"safe_eval"` because it performs an additional step to handle errors by calling another method of the chunks container object, `validate_is_ok`. If any error occurs during the evaluation of expressions pushed into the chunks container, the error is handled and stored in the `chunk` object that contains the expression, and thus no error is thrown to the calling environment.
It is still possible to push more code expressions into a chunks container that has already been evaluated. These newly added expressions may also modify the environment.

The most important function to check if everything went fine is `teal.code::chunks_is_ok`. It will return `TRUE` in the case where everything was fine.

`teal.code::chunks_validate_is_ok` returns a useful `validate(need(...))` message inside the shiny app in case something went wrong.

```{r, error=TRUE}
teal.code::chunks_is_ok()
teal.code::chunks_validate_is_ok()

# Trying an error inside a chunk ------------------------
teal.code::chunks_push(quote(stop("ERROR")))
teal.code::chunks_safe_eval()

teal.code::chunks_is_ok()
```{r}
teal.code::chunks_push(quote(z <- 10), chunks = chunks_container)
teal.code::chunks_get_rcode(chunks_container)

teal.code::chunks_validate_is_ok()
teal.code::chunks_safe_eval(chunks_container)
chunks_container$ls()
teal.code::chunks_get_var("z", chunks = chunks_container)
```

<img src="images/is_ok.png" alt="is_ok" style="width: 100%;"/>

The use of the `teal.code::chunks_safe_eval` is extremely important in a reactive context, such as inside the server function of a shiny app. Since errors are not thrown to the environment calling the chunks container, it will not crash the shiny app, which is a good thing. However, the shiny app will also not know that an error has occurred. Calling `teal.code::chunks_safe_eval` instead of the `eval` method of the chunks container ensures that a validation step occurs.
Note that code that have already been evaluated will not be re-evaluated when newly added code is evaluated.

## Tutorial Summary
```{r}
teal.code::chunks_push(quote(print("I will only be evaluated once")), chunks = chunks_container)
teal.code::chunks_safe_eval(chunks_container)

In summary:
teal.code::chunks_push(quote(rm(z)), chunks = chunks_container)

1. chunks containers host code snippets
2. chunks containers host their own environment
3. chunks containers are initialized inside shiny/teal using `teal.code::init_chunks`
4. chunks container can be accessed to retrieve variables from the environment using `teal.code::chunks_get_var`
5. chunks can be added to the chunks container by `teal.code::chunks_push`
6. All chunks inside a container can be executed by `teal.code::chunks_safe_eval`
7. `teal.code::chunks_validate_is_ok` and `teal.code::chunks_is_ok` allow checking for execution errors
# note that the string "I will only be evaluated once" is not printed again
teal.code::chunks_safe_eval(chunks_container)

The whole implementation of this tutorial is given in the *gif* below:
# z is removed
chunks_container$ls()
```

<img src="images/chunks_animation.gif" alt="chunks_animation" style="width: 100%;"/>
## Handling errors (and warnings)

The function `teal.code::chunks_safe_eval` is named `"safe_eval"` because it performs an additional step to handle errors by calling another method of the chunks container object, `validate_is_ok`. If any error occurs during the evaluation of expressions pushed into the chunks container, the error is handled and stored in the `chunk` object that contains the expression, and thus no error is thrown to the calling environment.

For more information about the implementation of chunks inside of shiny/teal module, please visit the Advanced chunks article.
The function `teal.code::chunks_is_ok` will return `TRUE` if all evaluated expressions in the chunks container evaluated without throwing an error.

Please find below the implicit vs. explicit usage of code chunks containers.
`teal.code::chunks_validate_is_ok` returns a useful `validate(need(...))` which a shiny app will use to display an error message in the UI instead of crashing the app.

---
```{r, error=TRUE}
teal.code::chunks_is_ok(chunks_container)
teal.code::chunks_validate_is_ok(chunks = chunks_container)

## Implementation of code chunks containers
# Trying an error inside a chunk ------------------------
teal.code::chunks_push(quote(stop("ERROR")), chunks = chunks_container)
teal.code::chunks_safe_eval(chunks_container)

There are two ways to initialize the code chunks inside shiny modules:
teal.code::chunks_is_ok(chunks_container)

- Using R6 implementation: `session_chunks <- chunks$new()`.
# internally, teal.code::chunks_safe_eval calls teal.code::chunks_validate_is_ok before returning
teal.code::chunks_validate_is_ok(chunks = chunks_container)
```

- Using `teal.code` wrappers: `teal.code::init_chunks()`.
<img src="images/is_ok.png" alt="is_ok" style="width: 100%;"/>

The `teal.code` functions can be used in both cases:
The use of the `teal.code::chunks_safe_eval` is good practice in a reactive context. Since errors are not thrown to the environment calling the chunks container, it will not crash the shiny app, which is a good thing. However, the shiny app will also not know that an error has occurred. Calling `teal.code::chunks_safe_eval` instead of the `eval` method of the chunks container ensures that a validation step occurs.

## Tutorial Summary

```{r}
session <- init_session()
teal.code::init_chunks()
a <- 1
b <- 2

# set a & b in env
teal.code::chunks_reset()
# push to chunks
teal.code::chunks_push(expression = bquote(a <- a + 1))
# eval gives return value
teal.code::chunks_safe_eval()
stopifnot(teal.code::chunks_get_var("a") == 2)

teal.code::chunks_push(expression = bquote(c <- a + b))
teal.code::chunks_safe_eval()
stopifnot(teal.code::chunks_get_var("c") == 4)
teal.code::chunks_push(expression = quote(a + b + c))
stopifnot(teal.code::chunks_safe_eval() == 8)
# create a new chunks object explicitly
chunks2 <- teal.code::chunks$new()
# push into this object
teal.code::chunks_push(bquote(d <- 1), chunks = chunks2)
# push the whole of chunks2 into our main chunks object
teal.code::chunks_push_chunks(chunks2)
# evaluate and get results
teal.code::chunks_safe_eval()
teal.code::chunks_get_var("d")
```
In summary:

The _default_ `chunks` object in many `teal.code` functions is the `chunks` object coupled to the shiny session. But as shown above it is possible to use other `chunk` objects with these functions using their `chunks` argument. The `chunks` object is an R6 object and consult the function documentation for details of its explicit methods.
1. chunks containers host code snippets
2. chunks containers host their own environment
3. chunks container can be accessed to retrieve variables from the environment using `teal.code::chunks_get_var`
4. expressions can be added to the chunks container by `teal.code::chunks_push`
5. code inside a container is executed by its `eval` method.
6. `teal.code::chunks_is_ok` allows checking for execution errors
7. `teal.code::chunks_validate_is_ok` allows a shiny app to display a validate message of the errors in the UI
8. `teal.code::chunks_safe_eval` will evaluate all snippets of the chunks container and then call `validate(need(...))` to let a shiny app silently handle any errors that occurred so that error messages can be outputted in the UI.

For more information about the implementation of chunks inside of shiny/teal module, please go on to the Advanced chunks article.
Binary file removed vignettes/images/chunks_animation.gif
Binary file not shown.
Binary file added vignettes/images/initialize_env.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified vignettes/images/is_ok.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified vignettes/images/reset.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.