Skip to content

Commit f62253d

Browse files
committed
add easier to read TA remarks
1 parent 7f657dd commit f62253d

File tree

1 file changed

+197
-0
lines changed

1 file changed

+197
-0
lines changed

lexicalscoping.md

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# Understanding Lexical Scoping in R – Great guidance by community TA in Coursera
2+
3+
## Gregory D. HorneCommunity TA· 5 days ago
4+
5+
I will show you the output from the sample functions to illustrate their behaviour. From the output you should be able to discern the logic within the R code. The functions we are expected to implement to handle invertible matrices follows the exact same logic. The sample run show below can be adapted to the new functions for matrices and the behaviour will be the same.
6+
7+
```R
8+
> a <- makeVector(c(1,2,3,4))
9+
> a$get()
10+
[1] 1 2 3 4
11+
> a$getmean()
12+
NULL
13+
> cachemean(a)
14+
[1] 2.5
15+
> a$getmean() # this is only to show you that the mean has been stored and does not affect anything
16+
[1] 2.5
17+
> cachemean(a)
18+
getting cached data
19+
[1] 2.5
20+
> a$set(c(10,20,30,40))
21+
> a$getmean()
22+
NULL
23+
> cachemean(a)
24+
[1] 25
25+
> cachemean(a)
26+
getting cached data
27+
[1] 25
28+
> a$get()
29+
[1] 10 20 30 40
30+
> a$setmean(0) # do NOT call setmean() directly despite it being accessible for the reason you will see next
31+
> a$getmean()
32+
[1] 0 # obviously non-sense since…
33+
> a$get()
34+
[1] 10 20 30 40
35+
>cachemean(a)
36+
[1] 0 # as you can see the call to setmean() effectively corrupted the functioning of the code
37+
> a <- makeVector(c(5, 25, 125, 625))
38+
> a$get()
39+
[1] 5 25 125 625
40+
> cachemean(a)
41+
[1] 195
42+
> cachemean(a)
43+
getting cached data
44+
[1] 195
45+
```
46+
47+
Understanding the concepts in terms of the behaviour of the two functions takes more than a few minutes because you have to not only read the existing exemplar functions but take some time to play with the functions. Often seeing the code in action is more helpful than reading a chapter about lexical scoping.
48+
49+
Variable m is declared uniquely in both functions and are allocated separate addresses in memory. In function makeVector() you’ll notice variable m is declared immediately and assigned the value NULL using the standard assignment operator (<-). However, the “set” functions defined within the containing makeVector() function require the special assignment operator (<–) to update the value of variable m; it is important to remember variable m was declared and initialised by makeVector().
50+
51+
Had functions set() and setmean() not used the special assignment operator, these functions would have allocated memory to store the value and labelled the address as m. The variables named m would effectively be isolated and distinct variables.
52+
53+
## <- Operator Versus <<- Operator
54+
55+
assignment operator: <-
56+
57+
superassignment operator: <<-
58+
59+
```R
60+
crazy <- function() {
61+
x <<- 3.14 # variable x in the containing environment (global in this case) is updated to be 3.14
62+
print(x) # since no local variable ‘x’ exists within function ‘crazy’ R searches the containing environments
63+
{ print(x); # this is to demonstrate the function, not a code block, is the smallest environment in R
64+
x <- 42; print(x) # local variable ‘x’ is declared (created) and assigned the value 42; overrides the variable ‘x’ in
65+
} # the containing environment
66+
print(x) # since local variable ‘x’ now exists within the function there is no need to search the containing
67+
} # environment (global in this case)
68+
> x <- 0
69+
> crazy()
70+
3.14
71+
3.14
72+
42
73+
42
74+
> x # variable ‘x’ outside of the function its updated value after the first statement within function ‘crazy()’
75+
[1] 3.14
76+
```
77+
78+
The first two print() statements use the variable ‘x’ in the containing environment, as no local variable ‘x’ exists at the moment, which has been updated from x <- 0 to x <- 3.14 via x <<- 3.14 inside function ‘crazy()’.
79+
80+
The third print() statement uses the variable ‘x’ just created by the preceding assignment statement x <- 42 which causes the containing environment not to be searched unlike the first and second print() statements.
81+
82+
The fourth print() statement uses the variable ‘x’ which exists within the function because the x <- 42 now masks access, at least for anything other than the super-assignment operator, to the containing environment’s variable ‘x’.
83+
84+
I added a call to variable ‘x’ after the function ‘crazy()’ returns to show it keeps the new value assigned to it by the super-assignment operator inside function ‘crazy()’.
85+
86+
The super-assignment operator does not update a variable of the same name inside an inner function but the innermost environment inherits any changes unless a local variable of the same name exists within the inner function as demonstrated by x <- 42; print(x) and print(x).
87+
Furthermore, if a variable named ‘x’ had existed inside function ‘crazy()’ and preceded the call to the super-assignment operator, the results would be as shown in the next example.
88+
89+
```R
90+
crazy <- function() {
91+
x <- 42
92+
x <<- 3.14
93+
print(x)
94+
}
95+
> x <- 0
96+
> x
97+
[1] 0
98+
> crazy()
99+
42
100+
> x
101+
[1] 3.14
102+
```
103+
104+
To reiterate the concept the next section explains in more detail the role and behaviour of the “superassignment” operator which allows the programmer to modify a variable declared outside of the current function in which the reference to the variable is made.
105+
In the simplest example consider how variable ‘x’ changes when the crazy() function is called.
106+
107+
```R
108+
# Declare and define a function named crazy()
109+
crazy <- function() { # create a new environment with a local variable ‘x’ and access to another variable ‘x’
110+
# declared somewhere outside this function
111+
x <- 3.14 # assign the numeric value 3.14 to local variable ‘x’
112+
print(x) # output the current value of local variable ‘x’ (1)
113+
{ print(x); # output the current value of local variable ‘x’ (2)
114+
x <<- 42; # assign the numeric value 42 to variable ‘x’ declared outside this function (3)
115+
print(x) # output the current value of local variable ‘x’ (4)
116+
}
117+
print(x) # output the current value of local variable ‘x’ (5)
118+
}
119+
> x <- 0 # Declare and define a local variable named ‘x’
120+
> x # output the current value of local variable ‘x’
121+
0
122+
> crazy() # Call function crazy()
123+
3.14 # (1) inner variable ‘x’
124+
3.14 # (2) inner variable ‘x’
125+
3.14 # (4) inner variable ‘x’
126+
3.14 # (5) inner variable ‘x’
127+
> x # (3) containing environment variable ‘x’
128+
42
129+
```
130+
131+
The curly braces (brackets) are intended to highlight the fact in R the smallest unit of lexical scoping is the function. Unlike some programming languages such as C that allow block-level variable scope, R treats a block enclosed in brackets as part of the nearest function.
132+
133+
```R
134+
x <- 3.14
135+
{ x <<- 42 }
136+
```
137+
is treated as though it is as shown below.
138+
139+
```R
140+
x <- 3.24 # assigns the value 3.14 to local variable ‘x’ not the variable ‘x’ in the containing environment
141+
x <<- 42 # assigns the value 42 to variable ‘x’ in the containing environment
142+
```
143+
144+
Perhaps the crude graphic can illuminate the effects of lexical scoping on variables a better than mere words.
145+
```
146+
+———————————+
147+
| (1a) x <- 0 becomes 42 | outer function / by default the global lexical scope is an anonymous function
148+
| (1b) x <- 42 by |
149+
| (3b) x <<- 42 |
150+
| +—————————–+ |
151+
| | (2) x <- 3.14 | | inner function
152+
| | (3a) x <<- 42 does | |
153+
| | not affect | |
154+
| | (2) | |
155+
| +—————————–+ |
156+
+———————————+
157+
```
158+
159+
Flow of execution is (1a) -> [(2) & (3a)] -> [(3b) & (1b)] -> (1a). I used the labels “inner function” and “outer function” because the same rules apply to nested functions.
160+
161+
## Unit tests (with expected output) for Programming Assignment 2
162+
163+
### Example
164+
```R
165+
> source(“cachematrix.R”)
166+
> amatrix = makeCacheMatrix(matrix(c(1,2,3,4), nrow=2, ncol=2))
167+
> amatrix$get() # Returns original matrix
168+
[,1] [,2]
169+
[1,] 1 3
170+
[2,] 2 4
171+
> cacheSolve(amatrix) # Computes, caches, and returns matrix inverse
172+
[,1] [,2]
173+
[1,] -2 1.5
174+
[2,] 1 -0.5
175+
> amatrix$getinverse() # Returns matrix inverse
176+
[,1] [,2]
177+
[1,] -2 1.5
178+
[2,] 1 -0.5
179+
> cacheSolve(amatrix) # Returns cached matrix inverse using previously computed matrix inverse
180+
getting cached data
181+
[,1] [,2]
182+
[1,] -2 1.5
183+
[2,] 1 -0.5
184+
> amatrix$set(matrix(c(0,5,99,66), nrow=2, ncol=2)) # Modify existing matrix
185+
> cacheSolve(amatrix) # Computes, caches, and returns new matrix inverse
186+
[,1] [,2]
187+
[1,] -0.13333333 0.2
188+
[2,] 0.01010101 0.0
189+
> amatrix$get() # Returns matrix
190+
[,1] [,2]
191+
[1,] 0 99
192+
[2,] 5 66
193+
> amatrix$getinverse() # Returns matrix inverse
194+
[,1] [,2]
195+
[1,] -0.13333333 0.2
196+
[2,] 0.01010101 0.0
197+
```

0 commit comments

Comments
 (0)