R Function Behavior: Scope, Nesting & Recursion
The Magic Treehouse Analogy
Imagine you have a magic treehouse with special rooms. Each room has its own toys, and some rooms are inside other rooms. The treehouse has rules about which toys you can play with depending on which room you’re in!
1. Function Scope: Where Can You Play?
What is Scope?
Scope is like asking: “Which toys can I see from here?”
In R, when you create a variable inside a function, it’s like putting a toy in a room. That toy only exists in THAT room!
The Two Types of Scope
# GLOBAL scope (the big living room)
my_toy <- "teddy bear"
play_room <- function() {
# LOCAL scope (your bedroom)
secret_toy <- "robot"
print(my_toy) # Can see!
print(secret_toy) # Can see!
}
play_room()
# [1] "teddy bear"
# [1] "robot"
print(secret_toy) # ERROR! Can't see!
Why Does This Happen?
Think of it this way:
- Global variables = Toys in the living room (everyone can see them)
- Local variables = Toys in YOUR room (only you can see them)
When you’re in your room, you can look out and see the living room toys. But from the living room, you can’t see inside your closed bedroom!
The Lookup Rule
R looks for variables like a child looking for a toy:
- First, check YOUR room (current function)
- If not found, check the living room (global environment)
- If still not found, check the garage (loaded packages)
x <- 10 # Living room toy
find_x <- function() {
x <- 5 # Bedroom toy (same name!)
return(x)
}
find_x() # Returns 5 (found in bedroom)
print(x) # Returns 10 (living room)
Modifying Global Variables
Want to change a living room toy from your bedroom? Use the super arrow <<-:
score <- 0 # Global
add_point <- function() {
score <<- score + 1 # Super arrow!
}
add_point()
add_point()
print(score) # [1] 2
Warning: Using
<<-is like reaching out of your room to move toys around. It works, but it can confuse others!
2. Nested Functions: Rooms Inside Rooms
What Are Nested Functions?
A nested function is like having a closet inside your bedroom, inside the treehouse. It’s a function defined INSIDE another function!
outer_room <- function() {
message <- "Hello from outer!"
inner_closet <- function() {
print(message) # Can see outer's toys!
print("Hello from inner!")
}
inner_closet()
}
outer_room()
# [1] "Hello from outer!"
# [1] "Hello from inner!"
The Nesting Magic
The inner function can see everything the outer function has:
make_multiplier <- function(n) {
# n is like a toy in this room
multiplier <- function(x) {
return(x * n) # Uses n from outer!
}
return(multiplier)
}
times_three <- make_multiplier(3)
times_three(5) # [1] 15
times_three(10) # [1] 30
Why Is This Useful?
Imagine you’re making a special calculator. The outer function sets up the rules, and the inner function does the work:
make_greeter <- function(greeting) {
greet <- function(name) {
paste(greeting, name, "!")
}
return(greet)
}
say_hello <- make_greeter("Hello")
say_howdy <- make_greeter("Howdy")
say_hello("Emma") # [1] "Hello Emma !"
say_howdy("Jake") # [1] "Howdy Jake !"
Each greeting function remembers its own special word!
Scope Chain Diagram
graph TD A["Global Environment"] --> B["outer_function"] B --> C["inner_function"] C --> D["innermost_function"] style A fill:#e8f5e9 style B fill:#fff3e0 style C fill:#e3f2fd style D fill:#fce4ec
The innermost function can see ALL the toys from every level above it!
3. Recursive Functions: The Mirror Trick
What Is Recursion?
Imagine standing between two mirrors. You see yourself, and then yourself again, and again, smaller each time until it stops.
A recursive function is a function that calls ITSELF!
The Simplest Example: Countdown
countdown <- function(n) {
if (n <= 0) {
print("BLAST OFF!")
return()
}
print(n)
countdown(n - 1) # Calls itself!
}
countdown(3)
# [1] 3
# [1] 2
# [1] 1
# [1] "BLAST OFF!"
The Two Magic Rules
Every recursive function needs:
- Base Case - When to STOP (the ground floor)
- Recursive Case - How to get closer to stopping
factorial <- function(n) {
# Base case: stop here!
if (n <= 1) {
return(1)
}
# Recursive case: call myself
return(n * factorial(n - 1))
}
factorial(5) # 5 * 4 * 3 * 2 * 1 = 120
How Does It Work?
Think of it like stacking plates:
graph TD A["factorial#40;5#41;"] --> B["5 Ă— factorial#40;4#41;"] B --> C["4 Ă— factorial#40;3#41;"] C --> D["3 Ă— factorial#40;2#41;"] D --> E["2 Ă— factorial#40;1#41;"] E --> F["Returns 1"] F --> G["2 Ă— 1 = 2"] G --> H["3 Ă— 2 = 6"] H --> I["4 Ă— 6 = 24"] I --> J["5 Ă— 24 = 120"]
Real Example: Sum of Numbers
sum_to <- function(n) {
if (n == 1) {
return(1) # Base case
}
return(n + sum_to(n - 1))
}
sum_to(4) # 4 + 3 + 2 + 1 = 10
The Fibonacci Dance
Each Fibonacci number is the sum of the two before it:
fibonacci <- function(n) {
if (n <= 2) {
return(1) # Base case
}
return(fibonacci(n-1) + fibonacci(n-2))
}
fibonacci(7) # [1] 13
# Sequence: 1, 1, 2, 3, 5, 8, 13
Common Recursion Patterns
| Pattern | Example | Base Case |
|---|---|---|
| Countdown | f(n-1) |
n <= 0 |
| List walking | f(tail) |
empty list |
| Tree traversal | f(left) + f(right) |
leaf node |
| Divide & conquer | f(half) |
size = 1 |
Putting It All Together
Here’s a beautiful example combining all three concepts:
# Global counter
call_count <- 0
# Outer function with nested helper
make_counter <- function(max) {
# Nested recursive function
count_up <- function(current) {
call_count <<- call_count + 1
if (current > max) {
return("Done!")
}
print(current)
count_up(current + 1)
}
return(count_up)
}
my_counter <- make_counter(3)
my_counter(1)
# [1] 1
# [1] 2
# [1] 3
# [1] "Done!"
print(call_count) # [1] 4
Quick Summary
| Concept | What It Means | Remember |
|---|---|---|
| Scope | Where variables live | Rooms in a treehouse |
| Local | Inside function only | Your bedroom toys |
| Global | Everywhere | Living room toys |
| Nested | Function in function | Closet in bedroom |
| Recursive | Calls itself | Mirror reflection |
| Base Case | When to stop | The ground floor |
You Did It!
Now you understand:
- Why variables can be “invisible” sometimes (scope)
- How to put functions inside functions (nesting)
- How functions can call themselves (recursion)
These three concepts are like superpowers. Once you master them, you can solve problems that seemed impossible before!
Pro Tip: When writing recursive functions, ALWAYS write the base case first. It’s like making sure you have a parachute before jumping!
