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?