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
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
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
Design a function continent-next that computes the next continent for a sailing ship. The route of the sailing ship is influenced by the wind direction. The next continent depends on the current continent and the wind direction (the direction from which the wind originates). Among others, the following relations hold: Europe –northwind–> Africa, Europe –westwind–> Asia, Asia –eastwind–> Europe. Complete these relations using a world map. To simplify the exercise assume that the Americas and Australia do not exist, and that if a ship reaches Antarctica it sinks. Assume that the only wind directions (directions from which the wind originates) are east, west, north, and south, and that they do not change during a single journey of a sailing ship from one continent to another. Use suitable enumerations.