Skip to main content

Practice 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?