On this page:
4.1 Steps
4.1.1 Problem statement
4.1.2 Data definition
4.1.3 Function name
4.1.4 Parameter list
4.1.5 Function stub
4.1.6 Purpose statement
4.1.7 Examples and expected results
4.1.8 Function body
4.1.9 Testing
4.1.10 Review and revise
4.2 Exercises
8.2

4 Recipe for Enumerations

An enumeration type can represent one of a fixed number of distinct values. Examples of information of this type include music genres (pop, rock, classic, jazz, etc.), continents (Europe, Asia, Africa, etc.), and traffic light colors (red, green, yellow). Each individual enumerated item is a distinct value and does not carry any additional data. In PostFix, such types can be represented as symbols. Symbols are names that can be used to denote a value.

traffic-light.pf serves as the example for enumerations.

4.1 Steps

4.1.1 Problem statement

Write down the problem statement as a comment. The problem statement should answer these questions: What information needs to be represented? What should the function (to be implemented) do with the data? What cases need to be considered?

#<

Design a function that returns the next color of a traffic light given the

current color of the traffic light.

>#

4.1.2 Data definition

Write how the domain information should be represented in the program. Conversely, write how to interpret the data (e.g., a number) as real-world information (e.g., a traffic light state). Write the data definition as the list of required symbols.

# enumeration of TrafficLight states:

# :red, :red-yellow, :green, :yellow

The colon : at the beginning or end of the symbol is necessary. Without the colon, PostFix tries to lookup the name in the dictionary. With the colon, the symbol stands for itself.

4.1.3 Function name

Conceive a descriptive function name. This should ideally be a short non-abbreviated name. You may revise the name and find a better name in the last step.

traffic-light-next:

4.1.4 Parameter list

Write down the function signature as a parameter list. The parameter names and types go left of the arrow (comma separated if you wish). The result type goes right of the arrow. The parameter names should ideally be descriptive, short, and non-abbreviated.

traffic-light-next: (traffic-light :Sym -> :Sym)

Remember, the purpose statement says that we need a function that returns the next color of the traffic light, given its current color.

4.1.5 Function stub

Write down the function stub, returning an arbitrary value from the range of the function. Check that the code is parsed without error.

traffic-light-next: (color :Sym -> :Sym) {

    :red

} fun

This function can already be called, but it gives us a traffic light that is constantly red.

4.1.6 Purpose statement

Write down a purpose statement (given as a comment). The purpose statement should describe what the function computes (not how it does that) and should mention the given inputs and produced result.

# Produces the next color of a traffic light

# given the current color of the traffic light.

4.1.7 Examples and expected results

Write down examples with expected results in the test function. Define any constants that the function needs. Check that the code is parsed without errors. (Some tests will fail for the stub.)

Examples:

If the traffic light is :red, expect :red-yellow as the next state.

If the traffic light is :red-yellow, expect :green as the next state.

If the traffic light is :green, expect :yellow as the next state.

If the traffic light is :yellow, expect :red as the next state.

Corresponding test cases in test function:

traffic-light-next-test: {

    :red traffic-light-next :red-yellow test=

    :red-yellow traffic-light-next :green test=

    :green traffic-light-next :yellow test=

    :yellow traffic-light-next :red test=

    test-stats

} fun

 

traffic-light-next-test

Running the above tests on the stub (which always returns :red) produces:

traffic-light.pf, line 14: Actual value :red differs from expected value :red-yellow.

traffic-light.pf, line 15: Actual value :red differs from expected value :green.

traffic-light.pf, line 16: Actual value :red differs from expected value :yellow.

traffic-light.pf, line 17: Check passed.

3 of 4 tests failed.

4.1.8 Function body

Implement the function body. Put required helper functions on a wish list. These will be implemented later.

How to identify the need for a helper function: A function should perform one well-defined task. A change in task or data type should be outsourced in a helper function. Moreover, a reusable subtask should be outsourced in a helper function (Don’t Repeat Yourself, DRY principle). It is often helpful to write a stub for the helper functions. This way you can already run the program.

The implementation of the traffic-light-next function does not need helper functions:

# Produces the next color of a traffic light

# given the current color of the traffic light.

traffic-light-next: (color :Sym -> :Sym) {

    color :red = {

        :red-yellow

    } {

        color :red-yellow = {

            :green

        } {

            color :green = {

                :yellow

            } {

                :red

            } if

        } if

    } if

} fun

4.1.9 Testing

Check that the function body satisfies the tests. Correct the function body (and the tests). Look for opportunities to simplify the structure of the code. This typically requires multiple iterations.

Running the tests on the implemented function produces:

traffic-light.pf, line 37: Check passed.

traffic-light.pf, line 38: Check passed.

traffic-light.pf, line 39: Check passed.

traffic-light.pf, line 40: Check passed.

All 4 tests passed!

4.1.10 Review and revise

Review and revise the function name, the parameter names, and the purpose statement. Improve them if necessary. A design is not complete until it has a purpose statement and tests. The purpose statement should describe what the function computes (not how it does the computation) and should mention the given inputs and produced result. The test examples should cover corner cases and a typical case.

In the given situation, the cond operator is a good alternative to the nested if operators.

traffic-light-next: (color :Sym -> :Sym) {

    {

        { color :red = } { :red-yellow }

        { color :red-yellow = } { :green }

        { color :green = } { :yellow }

        { color :yellow = } { :red }

    } cond

} fun

This can further be simplified using the cond-fun operator. It is a conditional nested in a function definition operator.

traffic-light-next: (color :Sym -> :Sym) {

    { color :red = } { :red-yellow }

    { color :red-yellow = } { :green }

    { color :green = } { :yellow }

    { color :yellow = } { :red }

} cond-fun

The complete program now looks like this:

#<

Design a function that returns the next color of a traffic light

given the current color of the traffic light.

>#

 

# enumeration of traffic light states:

# :red :red-yellow :green :yellow

 

traffic-light-next: (color :Sym -> :Sym) {

    { color :red = } { :red-yellow }

    { color :red-yellow = } { :green }

    { color :green = } { :yellow }

    { color :yellow = } { :red }

} cond-fun

 

traffic-light-next-test: {

    :red traffic-light-next :red-yellow test=

    :red-yellow traffic-light-next :green test=

    :green traffic-light-next :yellow test=

    :yellow traffic-light-next :red test=

    test-stats

} fun

 

traffic-light-next-test

Given this information, we can write a reusable template for functions that can handle traffic lights:

traffic-light-fn...: (color :Sym -> ...) {

    { color :red = } { ... }

    { color :red-yellow = } { ... }

    { color :green = } { ... }

    { color :yellow = } { ... }

} cond-fun

This template may be copied, pasted, and adapted for new functions that operate on this data type.

4.2 Exercises