Skip to main content

Recitation 11 — Variable Scoping and Mutable Data

Skills: 7, 8

Variable Scoping and Mutable Data

The "program directory" is our mental model of how a language keeps track of variable names and their associated values. Both Python and Pyret use this concept, but with key differences.

Key Differences:

  • Pyret: Must choose mutable (var x = 5) vs immutable (x = 5) at declaration
  • Python: Assignment = both creates and updates variables

Python Variable Scoping

Global vs Local Bindings

Example 1: Global Assignment

score = 100 # score is bound in the global directory

def show_score():
return score

print(show_score()) # prints 100

The function show_score looks up score in the global directory.

Example 2: Local Binding and Shadowing

score = 100

def change_score():
score = 50 # creates NEW local variable, doesn't change global
return score

print(change_score()) # prints 50
print(score) # prints 100 -- global unchanged

Using Global and Nonlocal

Example 3: Actually Updating Global

score = 100

def update_global_score():
global score # explicitly refer to global variable
score = 75

update_global_score()
print(score) # prints 75

Example 4: Nested Functions

def outer():
count = 10
def inner():
nonlocal count # refer to count from outer function
count = count + 5
inner()
return count

print(outer()) # prints 15

Exercise 1: Variable Scoping

What will be printed by each of these code blocks?

x = 5

def func1():
x = 10
return x

def func2():
global x
x = 15
return x

print(func1()) # What prints here?
print(x) # What prints here?
print(func2()) # What prints here?
print(x) # What prints here?

Pyret Variable Scoping

Immutable vs Mutable Declarations

Example 1: Immutable Declaration

x = 10
# x is immutable - this would cause an error:
# x := 20

Example 2: Mutable Declaration

var x = 10
x := x + 5 # x becomes 15
x # returns 15

What's the difference between = and := in Pyret?

No Accidental Shadowing

In Pyret, once a name is declared in a scope, re-declaring that same name is forbidden. This prevents accidental shadowing.

Example:

y = 20
# This would cause an error in Pyret:
# y = 30 (can't re-declare y)

# But this works:
var z = 20
z := 30 # explicit update

Exercise 2: Pyret Variable Declaration

Which of these Pyret code blocks will cause errors and why?

# Block A
x = 5
x := 10

# Block B
var y = 5
y := 10

# Block C
z = 5
z = 10

# Block D
var w = 5
w = 10

Memory Model

We expand our program directory model to include a "heap" -- where mutable structured data lives.

Mental Model:

  • Program Directory: Maps names to values (or addresses for structured data)
  • Heap: Stores structured data objects with unique addresses

Example:

  • Directory: book1 -> @1001
  • Heap: @1001: LibraryBook("Python Guide", 3)

Student Record Example

Data Design

Each student has an ID, name, and current credit hours. We want to update credits when they register for courses.

Python Version

from dataclasses import dataclass

@dataclass
class StudentRecord:
id: int
name: str
credits: int

student1 = StudentRecord(12345, "Alice Smith", 15)

student1.credits = student1.credits + 3 # registered for 3-credit course
print(student1.credits) # prints 18

Python doesn't require special syntax -- all fields are mutable by default.

Pyret Version

data StudentRecord:
student-record(id :: Number,
name :: String,
ref credits :: Number) # ref marks as mutable
end

student1 = student-record(12345, "Alice Smith", 15)

# Access mutable field with !
student1!credits # returns current credits

# Update credits using special syntax
student1!{credits: student1!credits + 3}
student1!credits # now returns 18

Key Differences:

  • Mutable fields marked with ref
  • Access with ! instead of .
  • Update with !{field: new-value} syntax

Exercise 3: Student Records

Create a Pyret data definition for a Course with mutable fields for enrollment count and current grade average. Then create a course instance and update its enrollment.

Aliasing and the Heap

Aliasing occurs when two names refer to the same object (same heap address).

Python Example:

student1 = StudentRecord(12345, "Alice Smith", 15)
student2 = student1 # aliasing -- both refer to same object

student1.credits = student1.credits + 3
print(student2.credits) # prints 18

Pyret Example:

student1 = student-record(12345, "Alice Smith", 15)
student2 = student1 # aliasing

student1!{credits: student1!credits + 3}
student2!credits # returns 18

Program Directory/Heap Visualization:

Directory:
student1 → @1001
student2 → @1001 # Same address

Heap:
@1001: StudentRecord(12345, "Alice Smith", 18)

Exercise 4: Aliasing Effects

What will be the final values of a, b, and c after this code runs?

from dataclasses import dataclass

@dataclass
class Point:
x: int
y: int

a = Point(1, 2)
b = a
c = Point(1, 2)

a.x = 5
b.y = 10

Equality: Structural vs Reference

Two Types of Equality

Structural Equality: Same contents in all fields Reference Equality: Same object in memory (same heap address)

Examples with Student Records

alice1 = StudentRecord(100, "Alice", 15)
alice2 = StudentRecord(100, "Alice", 15) # same content, different object
alice3 = alice1 # alias

# Structural equality (==)
print(alice1 == alice2) # True -- same contents
print(alice1 == alice3) # True -- same contents

# Reference equality (is)
print(alice1 is alice2) # False -- different objects
print(alice1 is alice3) # True -- same object (alias)

Mutation Effects on Equality

alice1.credits = 18 # update through one alias

print(alice1 == alice2) # now False -- different contents
print(alice1 is alice3) # still True -- same object
print(alice1.credits == alice3.credits) # True -- both show 18

Exercise 5: Equality and Mutation

What will each of these print statements output?

from dataclasses import dataclass

@dataclass
class Box:
contents: str

box1 = Box("apple")
box2 = Box("apple")
box3 = box1

print(box1 == box2) # True or False?
print(box1 is box2) # True or False?
print(box1 is box3) # True or False?

box1.contents = "orange"

print(box1 == box2) # True or False?
print(box1 is box3) # True or False?
print(box3.contents) # What value?

Wrap-Up

Why is understanding aliasing important when working with mutable data?