Do one thing, and do it well

In our first installment, we wrote several programs that really did nothing more than illustrate a concept. Let's turn the complexity up a notch and compose a program that actually solves a problem. The problem we are tasked with: given the high temperature of the past three days, compute the average and standard deviation.

To do this, we are going to need to implement an algorithm, the programming equivalent to a set of directions. It gives the major steps that one must take in order to solve a problem, but the details of how are left up to the programmer who implements the algorithm. For our problem at hand, we could write out our algorithm as follows:

Read in three values Compute the sum of these values Compute the average by dividing the sum by 3. Figure out how far each value is from the average. Add the square of the distances obtained in step four Take the square root of the value in step five Divide by the square root of 3

So, let us set out to implement our remedial algorithm in MHF:

PROGRAM weatherStation NUMBER temperature1 # tell the computer that we're going to need NUMBER temperature2 # space set aside for the three temperatures NUMBER temperature3 # plus space for the average and standard NUMBER averageTemp # deviation results NUMBER stddevTemp NUMBER sum, sqDist1, sqDist2, sqDist3 # we can define multiple # variables of the same type # on one line READ temperature1 # interface with someone or something READ temperature2 # to get our three temperature readings READ temperature3 # algorithm step 1 # algorithm step 2 sum = temperature1 + temperature2 + temperature3 # algorithm step 3 averageTemp = sum / 3.0 # steps 4 and half of 5 from the algorithm sqDist1 = (temperature1 - averageTemp)^2 sqDist2 = (temperature2 - averageTemp)^2 sqDist3 = (temperature3 - averageTemp)^2 # other half of 5 and step 6 from the algorithm stddevTemp = ((sqDist1+sqDist2+sqDist3)/3.0)^(0.5) PRINT "Average temperature = ", averageTemp, "+/-", stddevTemp END PROGRAM weatherStation

The weatherStation program follows the algorithm laid out above and calculates the average and the standard deviation of three temperature readings. But it's not possible to directly map the steps of the algorithm to the code. Some steps are split over multiple lines; other lines do more than a single step.

This example is reminiscent of real world development—as programs grow in complexity and size, it can be harder to track what individual steps are meant to accomplish.

To make the code easier to follow, and to allow the same operation to be done at multiple locations in the code, it's often useful to compartmentalize your code. All major programming languages (that I am aware of) support what are called functions, methods, or subroutines. These allow programmers to pull out related operations and place them in their own module, where they are separated from other parts of the program.

Functions, as we'll call them in our toy language, can be thought of like a mini-program within a program. They allow developers to isolate pieces of logic or complex operations and then refer to them by name later in the development cycle. In our weatherStation example, we could create one function for computing the average and another for computing the standard deviation.

One principle that drives developers is that each logical block of code should do one thing, and do it well—ideally without affecting things outside of its scope. Functions enable this to happen. Many programmers create collections of side-effect free functions; those that do what they are purported to do and nothing more. So a function to compute the average of three values would not format your hard drive, or order a pizza for you, or other less nefarious things (such as change the numbers themselves).

Let's take a look at how we could rework weatherStation to modularize some of its functionality. We see that there are three key things that occur in this program: we read in some values, calculate the average, and calculate the standard deviation. The latter two can easily be rolled into stand-alone functions.

A function in MHF will look a lot like a little program, it will start with the keyword FUNCTION, followed by the name of the function, then a parenthetical list of the inputs to the function. (These inputs are formally known as the arguments.) Finally, a function will contain a RETURNS keyword, which identifies any value that gets sent back when the function is complete. Let's look at a modularized weatherStation program

PROGRAM modularWeatherStation NUMBER temperature1 # tell the computer that we are going to need NUMBER temperature2 # space set aside for the three temperatures NUMBER temperature3 # plus space for the average and standard NUMBER averageTemp # deviation results. Just like before NUMBER stddevTemp READ temperature1 READ temperature2 READ temperature3 # we will call a function that computes the average for us # at this line, control of the program will be transferred # to the computeAverage function averageTemp = computeAverage( temperature1, temperature2, temperature3) # likewise for the standard deviation stddevTemp = standardDeviation( temperature1, temperature2, temperature3, averageTemp) PRINT "Average temperature = ", averageTemp, "+/-", stddevTemp END PROGRAM weatherStationRefactor FUNCTION computeAverage( NUMBER n1, NUMBER n2, NUMBER n3) RETURNS NUMBER NUMBER sum # the values passed into this function will be mapped to # the values n1, n2, and n3--only in this function sum = n1 + n2 + n3 # at a RETURN statement, the program will return to the # point where it left off RETURN sum/3.0 END FUNCTION computeAverage FUNCTION standardDeviation( NUMBER n1, NUMBER n2, NUMBER n3, NUMBER avg) RETURNS NUMBER NUMBER sqDist1, sqDist2, sqDist3 sqDist1 = (n1-avg)^2 sqDist2 = (n2-avg)^2 sqDist3 = (n3-avg)^2 RETURN ((sqDist1+sqDist2+sqDist3)/3.0)^(0.5) END FUNCTION standardDeviation

Program modularWeatherStation will produce the same results as our previous weatherStation program (assuming we give it the same three inputs). However, the ideas and implementation of the average and standard deviation are encapsulated in a function. The consumer of these functions—the main program—doesn't care what happens in the functions, as long as it gets the average back.

When the main program begins, it will set aside space for all the variables we tell it we will need then it will read in the three temperatures that we are interested in. When we reach line 15, we call the function computeAverage with the arguments temperature1, temperature2, and temperature3. Once the computer reaches this line of the main program, it will transfer its control to the computeAverage function, which is defined later.

In computeAverage, temperature1, temperature2, and temperature3 are now referred to by their local aliases, n1, n2, and n3. We define a local variable— variable that will only exist as long as the execution of the program is within the computeAverage function—for the sum. The algorithm for computing the average is carried out as before. Before we reach the final line in the computeAverage function, however, we encounter a RETURN statement.

On the line where we first describe the function, we also noted that it would return a NUMBER-type variable. In this case, the RETURN statement contains an expression that evaluates to a NUMBER-type variable. That value gets returned to the point in the main body of the program where the function call was made. The program will then continue to run from that point on as before.

Listing image by Aurich Lawson