Examples

  1. All functions, including helper functions, require examples. The only exception is if a function returns an Image. Examples are written in where blocks.

  2. Examples should generally be written before you start writing your function. Writing good examples will help you understand how your function should behave on different inputs.

  3. Examples should reflect various input scenarios so that you exercise the possible behaviors of your function. You don’t need to test every input or every input combination – which may be infinite! – but you should have enough to cover a wide range of your function’s possible behaviors.

  4. In particular, your examples should cover edge cases – inputs at the boundaries of particular ranges. For instance, many functions that have a number as an input should include an example where that input is 0.

As an example, let’s consider a function compare that takes two numerical inputs, x and y, and returns a string. It should return

  • "way less" if x is smaller than y - 2,
  • "about the same" if x is between y - 2 and y + 2 (inclusive), and
  • "way more" if x is greater than y + 2.

Here’s a set of good examples for this function:

fun compare(x :: Number, y :: Number) -> String:
  doc: "Describe the relationship between input numbers x and y"
  ...  # Function body elided
where:
  compare(1, 40) is "way less"
  compare(0, 3) is "way less"
  compare(8, 10) is "about the same"
  compare(1, 1) is "about the same"
  compare(0, 1) is "about the same"
  compare(17, 20) is "way more"
end

Notice that these examples cover each interesting case of the program. They also include edge cases testing the boundaries. For instance, compare(8, 10) is an edge case, since it’s at the boundary between the "way less" and "about the same" cases.

For most assignments, you can get a substantial amount of partial credit if you write good examples for a function even if your function doesn’t work correctly.

When writing examples before implementing a function, it may be helpful to include because clauses as a way of figuring out what the function body could look like:

fun times-four(x :: Number) -> Number:
  doc: "Multiplies input x by four"
  ...  # Function body elided
where:
  times-four(3) is 12 because 3 * 4
  times-four(0) is 0 because 0 * 4
  times-four(-1) is -4 because -1 * 4
end

Unless an assignment says otherwise, because clauses are not required – but they might be useful to you!

Design

  1. Use constants to capture relationships between values. For example, if you have rectangle(500, 300, "solid", "green") but mean for the height to be a scaled fraction of the width, you should instead write:

    width = 500
    rectangle(width, 3/5 * width, "solid", "green")
    
  2. Use helper functions to configure an expression that appears multiple times with slightly different arguments. For example,

    stairs =
      beside-align("bottom", rectangle(50, 50, "solid", "gray"),
        beside-align("bottom", rectangle(50, 100, "solid", "gray"),
          beside-align("bottom", rectangle(50, 150, "solid", "gray"),
            rectangle(50, 200, "solid", "gray"))))
    

    could be rewritten as:

    stair-width = 50
    
    fun stair(height :: Number) -> Image:
      doc: "Make an individual stair given its height"
      rectangle(stair-width, height, "solid", "gray")
    end
    
    stairs =
      beside-align("bottom", stair(50),
        beside-align("bottom", stair(100),
          beside-align("bottom", stair(150), stair(200))))
    
  3. Make sure your helper functions and constants are not redundant in light of existing built-in functions. For example, there is no point in making this function:

    fun string-lower(s :: String) -> String:
      doc: "Make a string lowercase"
      string-to-lower(s)
    end
    

    since you could always use string-to-lower instead.

Clarity

  1. Write docstrings for all functions, including helper and nested functions.

    A good docstring gives a description of the function, including its input(s) and output. Ideally, by looking at the docstring you know what the function does and how to use it without looking at the function body itself.

    fun three-stripes(bot-col :: String, mid-col :: String,
        top-col :: String) -> Image:
      doc: ```Makes a rectangular flag with three stripes.
        the inputs describe the stripes' colors, bottom-to-top.```
      ...
    end
    

    Bad docstring descriptions:

    • doc: "helper function for flag making"
    • doc: "output the right image for this problem"
    • doc: "uses above to stack images" – how your function body actually works is irrelevant to the docstring!
  2. Give constants and helper functions useful names. For instance,

    n1 = 3.1415
    n2 = 500
    n3 = 30
    
    fun helper(x):
      x + 3
    end
    

    would be better rewritten as

    pi = 3.1415
    flag-width = 500
    circle-radius = 30
    
    fun add-3(x :: Number) -> Number:
      doc: "Adds three to its input x"
      x + 3
    end
    
  3. All functions require type annotations on both inputs and output.

  4. Names of constants and functions should be lower-case and hyphen-separated. For configuration constants (for example the height of a character) it is acceptable to use all-caps hyphen-separated names.

    # Good Pyret style:
    flag-width = 500
    width-to-height-ratio = 3/5
    CHARACTER-HEIGHT = 40
    
    # Bad Pyret style:
    flag_width = 500
    widthHeightRatio = 3/5
    CharacterHeight = 40
    
  5. Keep lines under 80 characters.

    For very long lines, you will at some point see a vertical dashed blue line in Pyret. If you see this line, the lines in your program are definitely too long, and you need to add linebreaks (press enter at places in your code).

    The 80 character limit applies even when you have long strings and/or docstrings. Multiline docstrings can be written using backticks (`):

    fun f(x :: Number) -> Number:
      doc: ```Return 2; ignore inputs, always outputs 2 no matter
        what, this function is going to return 2```
      2
    end
    
  6. Indent your code properly. You can do this by selecting all (pressing ctrl-a on Linux and Windows or -a on macOS) and then tab.