Skip to main content

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

  1. Confusing data name and constructor name

    # BAD - using data name as constructor
    alice = Student("Alice", 19, 3.8) # should be student()
  2. Missing type annotations

    # BAD - no type information
    data Person:
    | person(name, age, email)
    end
  3. 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

  1. Using constructor name in type annotations

    # BAD - using constructor name instead of type name
    fun process-student(s :: student) -> String:
    s.name
    end
  2. 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

  1. 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
  2. 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:

  • cases must handle all possible variants
  • Variable names in patterns can be different from field names
  • Each case can have completely different logic
  • cases expressions return values that can be used in larger expressions

Common Student Mistakes to Watch For

  1. Missing cases for some variants

    # BAD - missing triangle case
    cases(Shape) shape:
    | circ(r) => 3.14 * r * r
    | rect(w, h) => w * h
    end
  2. Wrong syntax for cases

    # BAD - various syntax errors
    cases shape: # Missing (DataType)
    circ(r) => r * r
    | rect(w, h) -> w * h # Wrong arrow
    end
  3. 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."