Skip to main content

Skill 1: Designing Basic Functions (Pyret)

1. Type Annotations

What to Teach

Type annotations specify what types of data a function accepts and returns. They serve as contracts and documentation.

Basic Syntax

fun function-name(param1 :: Type1, param2 :: Type2) -> ReturnType:
# implementation
end

Common Types in Pyret

  • Number - integers and decimals
  • String - text values
  • Boolean - true/false values
  • Image - visual elements

Teaching Examples

Good Example:

fun calculate-tip(bill :: Number, rate :: Number) -> Number:
doc: "calculate tip amount based on bill total and tip rate"
bill * rate
end

What to Point Out:

  • Clear parameter names that indicate purpose
  • Appropriate types for the operation
  • Return type matches what the function produces

Common Student Mistakes to Watch For

  1. Missing type annotations entirely

    # BAD - no type information
    fun add(x, y):
    x + y
    end
  2. Wrong types for operations

    # BAD - trying to multiply strings
    fun repeat-word(word :: String, times :: String) -> String:
    word * times
    end
  3. Inconsistent return types

    # BAD - sometimes returns Number, sometimes String
    fun process-score(score :: Number) -> Number:
    if score >= 90:
    "A"
    else:
    score
    end
    end

2. Docstrings

What to Teach

Docstrings explain what a function does in plain English. They should be concise but complete.

Guidelines for Good Docstrings

  • Purpose-focused: What does this function accomplish?
  • Not redundant: Don't repeat what the type annotation already says
  • Avoid implementation details: Focus on what, not how

Teaching Examples

Good Example:

fun compound-interest(principal :: Number, rate :: Number, years :: Number) -> Number:
doc: "calculates final amount after compound interest is applied annually"
principal * num-expt(1 + rate, years)
end

Poor Examples and How to Fix Them:

# BAD - redundant with type annotation
fun add-numbers(x :: Number, y :: Number) -> Number:
doc: "takes two numbers and returns a number"
x + y
end

# BETTER
fun add-numbers(x :: Number, y :: Number) -> Number:
doc: "calculates the sum of two values"
x + y
end
# BAD - includes implementation details
fun is-even(n :: Number) -> Boolean:
doc: "uses modulo operator to check if remainder is 0 when divided by 2"
num-modulo(n, 2) == 0
end

# BETTER
fun is-even(n :: Number) -> Boolean:
doc: "determines whether a number is even"
num-modulo(n, 2) == 0
end

3. Test Cases with where Blocks

What to Teach

Tests are one of our primary design tools, and serve as a way of helping students to both understand problems and precisely specify what they should do. They also allow automatic validation of the code once it is written.

Types of Test Cases to Cover

  1. Common/typical cases - normal expected inputs
  2. Edge cases - boundary values (0, negative numbers, empty strings)
  3. Corner cases - unusual but valid inputs

Teaching Examples

Comprehensive Test Suite:

fun absolute-value(n :: Number) -> Number:
doc: "returns the absolute value of a number"
if n < 0:
n * -1
else:
n
end
where:
# common cases
absolute-value(5) is 5
absolute-value(42) is 42

# edge cases
absolute-value(0) is 0
absolute-value(-1) is 1
absolute-value(-100) is 100

# decimal cases
absolute-value(3.14) is 3.14
absolute-value(-2.5) is 2.5
end

Common Cases

Numbers:

  • Zero
  • Negative numbers
  • Very large numbers
  • Decimal numbers

Strings:

  • Empty string ""
  • Single character strings
  • Strings with spaces
  • Very long strings

Booleans:

  • Both true and false

4. Clean Implementation

What to Teach

Function bodies should be readable, efficient, and follow good naming conventions.

Key Principles

Clear Parameter Names:

# BAD
fun calc(x, y, z):
x * y * z
end

# GOOD
fun calculate-volume(length :: Number, width :: Number, height :: Number) -> Number:
doc: "calculates volume of a rectangular prism"
length * width * height
end

Avoid Unnecessary if Expressions:

# BAD - unnecessary if
fun is-positive(n :: Number) -> Boolean:
doc: "Determines if a number is positive"
if n > 0:
true
else:
false
end
end

# GOOD - direct boolean expression
fun is-positive(n :: Number) -> Boolean:
doc: "Determines if a number is positive"
n > 0
end

Common Teaching Scenarios

When Students Ask "Why do we need type annotations?"

"Type annotations help you think clearly about what your function does, help catch errors early, and make your code easier for others to understand and use."

When Students Write Overly Complex Docstrings

"Imagine explaining this function to a classmate in one sentence. What's the most important thing they need to know?"

When Students Skip Edge Case Testing

"What happens if someone passes 0 to your function? What about negative numbers? Testing these cases now prevents bugs later. And if you don't know what the output would be, it's easier to figure it out now then when you are deep in the code!"

When Students Write Messy Code

"Code is read more often than it's written. Make it easy for future you (and your teammates) to understand."