Skill 4: Structured & Conditional Data (Pyret)
1. Creating and Using Data with Multiple Fields
What to Teach
Structured data allows grouping related information into single units. Students need to understand data definition syntax, constructor usage, and field access.
Basic Data Definition Syntax
data Student:
| student(name :: String, age :: Number, gpa :: Number)
end
Creating Instances
alice = student("Alice Smith", 19, 3.8)
bob = student("Bob Jones", 20, 3.2)
Field Access with Dot Notation
# access individual fields
alice-name = alice.name
alice-age = alice.age
alice-gpa = alice.gpa
Teaching Examples
Good Example:
data Book:
| book(title :: String, author :: String, pages :: Number, available :: Boolean)
end
# create book instances
book1 = book("1984", "George Orwell", 328, true)
book2 = book("To Kill a Mockingbird", "Harper Lee", 281, false)
# Access fields
book1-title = book1.title
book1-pages = book1.pages
is-available = book1.available
What to Point Out:
- Data name starts with capital letter (
Student,Book) - Constructor name starts with lowercase (
student,book) - Field names should be descriptive
- Type annotations specify what each field contains
- Dot notation accesses fields:
instance.field-name
Common Student Mistakes to Watch For
-
Confusing data name and constructor name
# BAD - using data name as constructor
alice = Student("Alice", 19, 3.8) # should be student() -
Missing type annotations
# BAD - no type information
data Person:
| person(name, age, email)
end -
Wrong field access syntax
# BAD - various incorrect syntaxes
name = alice[name] # wrong - not array access
name = alice("name") # wrong - not function call
name = get-name(alice) # wrong - not a function
2. Type Annotations: Data Type vs Constructor
What to Teach
Students must distinguish between the data type (used in annotations) and constructor names (used to create instances).
Data Type vs Constructor
data Student:
| student(name :: String, age :: Number, gpa :: Number)
end
# type annotation uses data name (Student)
fun honor-student(s :: Student) -> Boolean:
doc: "determines if student qualifies for honors"
s.gpa >= 3.5
where:
honor-student(student("Alice", 19, 3.8)) is true
honor-student(student("Bob", 20, 3.5)) is true
honor-student(student("Charlie", 18, 3.4)) is false
honor-student(student("Diana", 21, 4.0)) is true
honor-student(student("Eve", 19, 2.5)) is false
end
# constructor uses lowercase name (student)
alice = student("Alice", 19, 3.8)
Function Parameters and Return Types
# parameter type annotation
fun get-student-info(s :: Student) -> String:
doc: "returns formatted student information"
s.name + " (Age: " + num-to-string(s.age) + ")"
where:
get-student-info(student("Alice", 19, 3.8)) is "Alice (Age: 19)"
get-student-info(student("Bob", 20, 3.5)) is "Bob (Age: 20)"
get-student-info(student("Charlie", 18, 3.4)) is "Charlie (Age: 18)"
end
# return type annotation
fun create-default-student() -> Student:
doc: "creates a student with default values"
student("Unknown", 18, 0.0)
where:
create-default-student() is student("Unknown", 18, 0.0)
end
Lists of Structured Data
# list type annotation
fun find-honor-students(students :: List<Student>) -> List<Student>:
doc: "filters list to include only honor students"
filter(lam(s): s.gpa >= 3.5 end, students)
where:
find-honor-students([list:
student("Alice", 19, 3.8),
student("Bob", 20, 3.2),
student("Charlie", 18, 3.7)]) is [list:
student("Alice", 19, 3.8),
student("Charlie", 18, 3.7)]
find-honor-students([list: student("Dave", 19, 2.5)]) is empty
find-honor-students(empty) is empty
find-honor-students([list: student("Eve", 20, 3.5)]) is [list: student("Eve", 20, 3.5)]
end
Teaching Examples
Good Example:
data Rectangle:
| rect(width :: Number, height :: Number)
end
# function takes Rectangle type, returns Number type
fun calculate-area(r :: Rectangle) -> Number:
doc: "calculates area of a rectangle"
r.width * r.height
where:
calculate-area(rect(5, 3)) is 15
calculate-area(rect(10, 2)) is 20
calculate-area(rect(1, 1)) is 1
calculate-area(rect(4, 4)) is 16
end
# function returns Rectangle type
fun make-square(side :: Number) -> Rectangle:
doc: "creates a square rectangle"
rect(side, side)
where:
make-square(5) is rect(5, 5)
make-square(1) is rect(1, 1)
make-square(10) is rect(10, 10)
end
# create instances using constructor
rect1 = rect(5, 3)
square1 = make-square(4)
What to Point Out:
- Type annotations use capitalized data name (
Rectangle) - Instance creation uses lowercase constructor (
rect) - Function signatures show expected data types
- Return type annotations help document what functions produce
Common Student Mistakes to Watch For
-
Using constructor name in type annotations
# BAD - using constructor name instead of type name
fun process-student(s :: student) -> String:
s.name
end -
Using data type name as constructor
# BAD - using type name instead of constructor
my-student = Student("Alice", 19, 3.8)
3. Data with Multiple Variants
What to Teach
Variant data types represent different categories of related data. Each variant can have different fields, allowing flexible data modeling.
Basic Variant Syntax
data Shape:
| circ(radius :: Number)
| rect(width :: Number, height :: Number)
| tri(base :: Number, height :: Number)
end
Creating Variant Instances
shape1 = circ(5)
shape2 = rect(4, 6)
shape3 = tri(3, 8)
Teaching Examples
Good Example:
data Vehicle:
| car(make :: String, model :: String, doors :: Number)
| motorcycle(make :: String, model :: String, engine-size :: Number)
| bicycle(brand :: String, gear-count :: Number)
end
# create different vehicle types
my-car = car("Toyota", "Camry", 4)
my-bike = motorcycle("Honda", "CRV", 600)
my-bicycle = bicycle("Trek", 21)
What to Point Out:
- Each variant is a different constructor
- Variants can have different numbers and types of fields
- All variants belong to the same data type
Common Student Mistakes to Watch For
-
Trying to access fields that don't exist on all variants
# BAD - not all shapes have width
fun get-width(shape :: Shape) -> Number:
shape.width
end -
Not understanding that variants are different
# BAD - assuming all variants have same fields
shapes = [list: circ(5), rect(3, 4)]
for each(shape from shapes):
area = shape.radius * shape.radius
end
4. Using cases for Variant Data
What to Teach
cases expressions is how we process data with variants, since different variants have different fields. Each variant can be processed differently based on its specific fields and meaning.
Basic cases Syntax
cases(DataType) instance:
| variant1(field1, field2) => expression1
| variant2(field3) => expression2
| variant3(field4, field5, field6) => expression3
end
Teaching Examples
Simple Cases Example:
data Shape:
| circ(radius :: Number)
| rect(width :: Number, height :: Number)
end
fun calculate-area(shape :: Shape) -> Number:
doc: "calculates area based on shape type"
cases(Shape) shape:
| circ(r) => 3.14159 * r * r
| rect(w, h) => w * h
end
where:
calculate-area(circ(1)) is-roughly 3.14159
calculate-area(circ(2)) is-roughly 12.56636
calculate-area(rect(5, 3)) is 15
calculate-area(rect(10, 2)) is 20
calculate-area(circ(0)) is 0
end
What to Point Out:
casesmust handle all possible variants- Variable names in patterns can be different from field names
- Each case can have completely different logic
casesexpressions return values that can be used in larger expressions
Common Student Mistakes to Watch For
-
Missing cases for some variants
# BAD - missing triangle case
cases(Shape) shape:
| circ(r) => 3.14 * r * r
| rect(w, h) => w * h
end -
Wrong syntax for cases
# BAD - various syntax errors
cases shape: # Missing (DataType)
circ(r) => r * r
| rect(w, h) -> w * h # Wrong arrow
end -
Not using extracted field values
# BAD - not using the extracted fields
cases(Shape) shape:
| circ(r) => shape.radius * shape.radius
| rect(w, h) => shape.width * shape.height
end
Common Teaching Scenarios
When Students Ask "Why use structured data instead of separate variables?"
"Structured data keeps related information together, making it easier to pass around and ensuring you don't accidentally mix up which age goes with which name."
When Students Confuse Type Names and Constructors
"Think of the data definition as creating two things: a type name (capitalized) for annotations, and constructor functions (lowercase) for making instances."
When Students Forget to Handle All Cases
"The computer needs to know what to do for every possible variant. If you miss one, your program won't know how to handle that case."