Skip to main content

Skill 3: Iteration with Lists (Pyret)

1. Understanding Mutable Variables with var

What to Teach

Mutable variables allow values to be changed during program execution. In Pyret, var creates variables that can be reassigned, unlike regular bindings which are immutable.

Basic Syntax

var variable-name = initial-value
variable-name := new-value # reassignment uses :=

When to Use var

In this class, the only place we will really use them is to accumulate values during iteration. While there are other uses, when they are necessary is more advanced than this class will get, and the basic uses cause more confusion and trouble.

Teaching Examples

Good Example:

var total = 0
var count = 0

# variables can later be updated, i.e., within the body of a for each() loop
total := total + 10
count := count + 1

What to Point Out:

  • var declares a mutable variable
  • := is used for reassignment (not =)
  • Initial value should match the type you'll be accumulating
  • Variable names should describe what they're accumulating

Common Student Mistakes to Watch For

  1. Using = instead of := for reassignment

    # BAD - using = for reassignment
    var sum = 0
    sum = sum + 5
  2. Trying to reassign regular bindings

    # BAD - regular bindings can't be changed
    total = 0
    total := 10 # total is not mutable

2. Basic for each Loop Structure

What to Teach

for each loops iterate through every element in a list, executing code for each element. Combined with mutable variables, they enable incremental construction of results.

Basic Syntax

for each(element from list):
# do something with element
end

Teaching Examples

Simple Example:

numbers = [list: 1, 2, 3, 4, 5]
var sum = 0

for each(num from numbers):
sum := sum + num
end

# sum is now 15

More Complex Example:

names = [list: "alice", "bob", "charlie"]
var result = ""

for each(name from names):
result := result + string-upper(name) + " "
end

# result is "ALICE BOB CHARLIE "

What to Point Out:

  • Loop variable (num, name) represents current element
  • Loop body executes once for each list element
  • Mutable variables accumulate results across iterations
  • Loop variable is only accessible inside the loop

Common Student Mistakes to Watch For

  1. Forgetting to initialize accumulator variables

    # BAD - sum not initialized
    for each(num from numbers):
    sum := sum + num # Error: sum is not defined
    end
  2. Using immutable bindings in loops

    # BAD - can't change total
    total = 0
    for each(num from numbers):
    total := total + num # total is not mutable
    end
  3. Wrong loop variable scope

    # BAD - trying to use loop variable outside loop
    for each(item from my-list):
    # process item
    end
    final-item = item # item not defined here

3. Common Accumulation Patterns

What to Teach

Different types of problems require different accumulation strategies. Students should recognize these patterns and choose appropriate initial values and update operations.

Sum Accumulation

scores = [list: 85, 92, 78, 96, 88]
var total = 0

for each(score from scores):
total := total + score
end

Product Accumulation

factors = [list: 2, 3, 4, 5]
var product = 1

for each(factor from factors):
product := product * factor
end

Counting Occurrences

grades = [list: "A", "B", "A", "C", "A", "B"]
var a-count = 0

for each(grade from grades):
if grade == "A":
a-count := a-count + 1
end
end

Building Strings

words = [list: "hello", "world", "from", "pyret"]
var sentence = ""

for each(word from words):
sentence := sentence + word + " "
end

Finding Maximum/Minimum

temperatures = [list: 72, 68, 75, 82, 79]
var max-temp = temperatures.first # Start with first element

for each(temp from temperatures):
if temp > max-temp:
max-temp := temp
end
end

What to Point Out:

  • Choose initial values carefully (0 for sum, 1 for product, empty for strings/lists)
  • Use conditionals inside loops for selective processing
  • Multiple variables can be updated in the same loop
  • Consider what happens on the first iteration

Common Student Mistakes to Watch For

  1. Wrong initial values

    # BAD - wrong starting value for product
    var product = 0
    for each(num from numbers):
    product := product * num
    end
  2. Not handling empty lists

    # BAD - assumes list always has elements
    var max-value = my-list.first

4. Building Complex Data Structures

What to Teach

Iteration can construct lists, strings, and other data structures element by element. Students should understand how to build new data from existing data using accumulation patterns.

Building Lists

numbers = [list: 1, 2, 3, 4, 5]
var squares = empty

for each(num from numbers):
squares := link(num * num, squares)
end

# squares is [list: 25, 16, 9, 4, 1] (reversed order)

Building Lists in Correct Order

numbers = [list: 1, 2, 3, 4, 5]
var squares = []

for each(num from numbers):
squares := squares + [list: num * num]
end

# squares is [list: 1, 4, 9, 16, 25] (correct order)

Teaching Examples

Complex Data Construction:

students = [list: "alice", "bob", "charlie", "diana", "eve"]
var long-names = []
var short-names = []
var total-length = 0

for each(name from students):
total-length := total-length + string-length(name)
if string-length(name) > 4:
long-names := link(string-upper(name), long-names)
else:
short-names := link(name, short-names)
end
end

What to Point Out:

  • link adds to front of list (fast but reverses order)
  • List concatenation + maintains order but is slower
  • Multiple data structures can be built simultaneously

4. Functions with Multiple Statements and block

What to Teach

When functions contain multiple statements (not including variable definitions), including for each loops with accumulators that need to be returned, they must be wrapped in a block in Pyret.

Basic block Syntax

fun calculate-sum(numbers :: List<Number>) -> Number:
doc: "calculates sum of all numbers in list"
block:
var total = 0
for each(num from numbers):
total := total + num
end
total # this is the second expression, would trigger error without block
end
where:
calculate-sum([list: 1, 2, 3, 4, 5]) is 15
calculate-sum([list: 10, 20]) is 30
calculate-sum(empty) is 0
calculate-sum([list: -5, 5]) is 0
end

Shorthand fun with block Syntax

fun calculate-sum(numbers :: List<Number>) -> Number block:
doc: "calculates sum of all numbers in list"
var total = 0
for each(num from numbers):
total := total + num
end
total
where:
calculate-sum([list: 1, 2, 3, 4, 5]) is 15
calculate-sum([list: 10, 20]) is 30
calculate-sum(empty) is 0
calculate-sum([list: -5, 5]) is 0
end

Teaching Examples

Good Example - Function with Accumulation:

fun count-vowels(words :: List<String>) -> Number block:
doc: "counts total vowels across all words"
var vowel-count = 0
for each(word from words):
for each(char from string-explode(word)):
if string-contains("aeiouAEIOU", char):
vowel-count := vowel-count + 1
else:
nothing
end
end
end
vowel-count
where:
count-vowels([list: "hello", "world"]) is 3
count-vowels([list: "aeiou"]) is 5
count-vowels([list: "xyz"]) is 0
count-vowels(empty) is 0
count-vowels([list: "HELLO", "WORLD"]) is 3
end

Good Example - Building Data Structures:

fun process-grades(scores :: List<Number>) -> List<String> block:
doc: "converts numeric scores to letter grades"
var grades = empty
for each(score from scores):
if score >= 90:
grades := grades + [list: "A"]
else if score >= 80:
grades := grades + [list: "B"]
else if score >= 70:
grades := grades + [list: "C"]
else:
grades := grades + [list: "F"]
end
end
grades
where:
process-grades([list: 95, 85, 75, 65]) is [list: "A", "B", "C", "F"]
process-grades([list: 90, 80, 70]) is [list: "A", "B", "C"]
process-grades([list: 100]) is [list: "A"]
process-grades(empty) is empty
process-grades([list: 50, 60]) is [list: "F", "F"]
end

When block is Required

  • Functions with for each loops that need to return accumulated values
  • Functions with multiple statements beyond variable definitions
  • Functions that need to perform sequential operations before returning

When block is NOT Required

# Simple expression - no block needed
fun double(x :: Number) -> Number:
doc: "doubles a number"
x * 2
where:
double(5) is 10
double(0) is 0
double(-3) is -6
end

# Single variable definition + expression - no block needed
fun circle-area(radius :: Number) -> Number:
doc: "calculates area of circle"
pi = 3.14159
pi * radius * radius
where:
circle-area(1) is-roughly 3.14159 within 0.001
circle-area(2) is-roughly 12.56636 within 0.001
circle-area(0) is 0
end

Common Student Mistakes to Watch For

  1. Forgetting block with loops

    # BAD - missing block
    fun sum-list(numbers :: List<Number>) -> Number:
    var total = 0
    for each(num from numbers):
    total := total + num
    end
    total
    end
  2. Using block unnecessarily

    # BAD - block not needed for simple expression
    fun add-one(x :: Number) -> Number block:
    x + 1
    end
  3. Forgetting to return the accumulator

    # BAD - loop runs but nothing is returned
    fun bad-sum(numbers :: List<Number>) -> Number block:
    var total = 0
    for each(num from numbers):
    total := total + num
    end
    # missing: total
    end

Common Teaching Scenarios

When Students Ask "Why not just use map/filter?"

"Iteration with for each gives you more control and lets you build multiple results at once. Sometimes you need that flexibility, especially when accumulating complex state."

When Students Forget to Initialize Variables

"Think about what value makes sense before the loop starts. For sums, start with 0. For products, start with 1. For finding max, start with the first element."

When Students Mix Up := and =

"Remember: = creates a new binding, := changes an existing var. If you declared with var, you must update with :=."