Lexical Scoping in R

(Textbook: Section 16.3)

The Clown Problem

A Gift-Giving Clown

clown <- function() {
  bag <- c("mustang", "goat", "20 dollar bill")
  cat("Hello, my name is Alfie!\n")
  if (length(bag) == 0) {
    sad_message <-"Sorry, the bag is empty!"
    return(cat(sad_message))
  }
  gift_number <- sample(1:length(bag), size = 1)
  gift <- bag[gift_number]
  cat("Here, have a ", gift, "!", sep = "")
  cat("\n\n")
  # clown removes gift from bag:
  bag <- bag[-gift_number]
}

Go ’Round Town, Giving Gifts

clown()
Hello, my name is Alfie!
Here, have a mustang!
clown()
Hello, my name is Alfie!
Here, have a mustang!
clown()
Hello, my name is Alfie!
Here, have a goat!
clown()
Hello, my name is Alfie!
Here, have a goat!

Issue: Bag Doesn’t Change

The problem is: every time clown() is run, we start with a full bag.

This line:

bag <- bag[-gift_number]

was useless!

Solution Attempt: Super-Assignment

Learn about <<-:

help("<<-")

<<- causes:

“a search to be made through parent environments for an existing definition of the variable being assigned. If such a variable is found (and its binding is not locked) then its value is redefined, otherwise assignment takes place in the global environment.”

Variant Clown Function

## the bag is defined in the Global Environment:
bag <- c("mustang", "goat", "20 dollar bill")

## the new clown-giving function
clown <- function() {
  cat("Hello, my name is Alfie!\n")
  if (length(bag) == 0) {
    sad_message <-"Sorry, the bag is empty!"
    return(cat(sad_message))
  }
  gift_number <- sample(1:length(bag), size = 1)
  gift <- bag[gift_number]
  cat("Here, have a ", gift, "!", sep = "")
  cat("\n\n")
  ## use the super-assignment operator to change bag
  bag <<- bag[-gift_number]
}

Try It Out:

clown()
Hello, my name is Alfie!
Here, have a goat!
clown()
Hello, my name is Alfie!
Here, have a mustang!
clown()
Hello, my name is Alfie!
Here, have a 20 dollar bill!
clown()
Hello, my name is Alfie!
Sorry, the bag is empty!

Great, But …

  • It’s dangerous to have a function messing with items in other environments. (What if you had some other use for an object named bag?)
  • If you have than one clown, you would have to have a differently-named bag for each of them.

Idea

Let’s make use of an important rule in R:

In R, the parent environment of the runtime environment of a function is the environment in which the function was defined.

This is an instance of lexical scoping.

Example

y <- 10
f <- function(x) {
  x + y
}
f(15)
[1] 25
g <- function(x) {
  y <- 5
  f(x)
}
g(15)
[1] 25

Observe

  • g(15) was NOT \(15 + 5 = 20\)
  • g(15) was \(15 + 10 = 25\)

That’s because the parent environment of f() is the Global Environment (where f() was defined).

The parent environment of f() was NOT the runtime environment of g(). (That was just an environment in which F() was called.)

Application: A Clown-Factory Function

## a function to make clowns
clown_factory <- function(name, bag = c("million dollars", "new car")) {
  clown <- function() {
    cat("Hello, my name is ", name, "!\n", sep = "")
    if (length(bag) == 0) {
      sad_message <-"Sorry, the bag is empty!"
      return(cat(sad_message))
    }
    gift_number <- sample(1:length(bag), size = 1)
    gift <- bag[gift_number]
    cat("Here, have a ", gift, "!\n", sep = "")
    cat("\n\n")
    ## use the super-assignment operator to change bag
    bag <<- bag[-gift_number]
  }
  clown
}   

Make Some Clowns

## clowns will issued bags like this:
bag <- c("mustang", "goat", "20 dollar bill")
## make three clowns:
alfie <- clown_factory(name = "Alfie", bag = bag)
bozo <- clown_factory(name = "Bozo")
chester <- clown_factory(name = "Chester",bag = bag)

Clowns Go ’Round Town

alfie()
Hello, my name is Alfie!
Here, have a mustang!
alfie()
Hello, my name is Alfie!
Here, have a goat!
alfie()
Hello, my name is Alfie!
Here, have a 20 dollar bill!
alfie()
Hello, my name is Alfie!
Sorry, the bag is empty!
bozo()
Hello, my name is Bozo!
Here, have a new car!
chester()
Hello, my name is Chester!
Here, have a goat!

Check the Global Bag

bag
[1] "mustang"        "goat"           "20 dollar bill"

It is not affected by the actions of the clowns!

Why?

For each clown, its bag is in the run-time environment where the clown was defined. (That’s three different run-time environments!)

Each clown searches parents for bag.

Each clown finds bag in its (own) parent environment.

Recursion?

What does this have to do with recursion? We’ll find out!