forked from hadley/mastering-shiny
-
Notifications
You must be signed in to change notification settings - Fork 0
/
_remnants.Rmd
71 lines (51 loc) · 3.4 KB
/
_remnants.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
## Prerequisite 1: Understanding lexical scoping
Before we can understand reactive programming, we need to understand *lexical scoping*. "Scoping" means the algorithm a programming language uses to look for variables, and lexical scoping is by far the most common type of scoping among languages commonly used today.
If you've written a function before, you know that variables you define inside a function body are local to that function:
```{r error=TRUE}
func1 <- function() {
# A local variable
x <- 1
print(x)
}
func1()
x
```
In this example, the `x` variable is defined inside the function, so attempts to read this variable from outside the function fail.
Hopefully, you're also familiar with global variables. These can be read from inside of any function, unless a variable of the same name has been defined inside the function.
```{r}
# A global variable (defined outside any function)
x <- 1
func2 <- function() {
print(x) # prints `1`
x <- 2
print(x) # prints `2`
}
func2()
x # The global variable is still `1`
```
You can get quite far as an R programmer knowing only about global and local variable scopes. What you may not have realized is that there can be additional scopes between the global and local ones! These intermediate scopes play a particularly important role in Shiny apps, as we'll soon see.
The body of the following `createCounter` function defines two functions: `value` and `increment`. When a function is defined inside of another function, we call it a _nested function_.
```{r}
createCounter <- function(startingValue = 0) {
counter <- startingValue
value <- function() {
counter
}
increment <- function() {
counter <<- counter + 1L
}
list(
value = value,
increment = increment
)
}
counter1 <- createCounter()
counter1$value()
counter1$increment()
counter1$value()
```
The `value` function returns the value of `counter`. In this case, `counter` is present in neither the local scope (i.e. defined inside of the `value` function) nor the global scope (i.e. defined outside of any function), but in a _parent scope_, that is, the local variable scope of the containing function. This type of parent/child scoping is also recursive, meaning that if the nested function `value` itself defined a (doubly-)nested function, that new function would also have access to the `counter` variable.
Think of it this way. The line `counter <- startingValue` defines a variable. To figure out where that variable may be used from, scan backwards for the opening curly brace `{` that directly contains this line of code. All of the code between that opening curly brace and its matching closing curly brace `}` is able to read `counter`, including from inside of nested functions. Any functions whose text appears outside of those braces cannot read `counter`.
This is the way things work by default in R, and it's called _lexical scoping_. It's called "lexical" because you can figure out the relationships between different variable scopes by looking at the positions of curly braces in the source code. (In case you're curious, the other type of scoping is called "dynamic scoping" and it's also used in R, though in smaller doses. See the `base::with` function for one example of dynamic scoping.)
Shiny relies heavily on lexical scoping to allow reactive inputs, expressions, outputs, and observers to have relationships with each other, as we'll soon see.
## Prerequisite 2: Side effects