Good Things Come in Small Packages

Procedures in Modula 3

In the first set of these notes we saw how large problems are often best approached by breaking them down successively into collections of smaller tasks. The basic building blocks that we use in Modula 3 are procedures. A procedure may take some inputs (`input arguments', `input parameters') and may produce some outputs (`results', `output arguments', `output parameters'). NB: It is important to distinguish between these terms and the input and output that is performed with the Get and Put interfaces. Here we are not talking about input/output between the program and file or screen. Each procedure that we write should have a `specification' associated with it. Sometimes this specification will be the whole problem to be solved (e.g. an exercise that has been set) and sometimes it will be one sub-task that has arisen out of our problem analysis and design. Consider the following problem:

Write a procedure to convert a sum of money in a local currency into ECUs.

How should we approach solving this problem? Step 1 is Understand the Problem.

The procedure could assume a particular exchange rate. This is inflexible. Rates change and different countries have different rates, so the procedure would have to be continuously rewritten to cope with these factors.
The procedure could prompt the user to input the exchange rate each time it is called. This would work, but it isn't very flexible. In general, it is best to make procedures as self-contained as possible (in Software Engineering terminology this is called `loose coupling') and avoid interactive input/output wherever possible.
The most flexible approach is to provide the exchange rate as a second input parameter.

We should rewrite the specification as follows:

Write a procedure to convert a sum of money in a local currency into ECUs, given the exchange rate from the local currency.

Now we can write the procedure's header, even though we haven't thought at all about how the calculation will be performed. Here is one way to write the header:


Procedure ConvertToEcus(Amount, ExchangeRate : REAL) : REAL

We have two inputs, so there are two input parameters and we have one output so there is a result. The type of all of these values is REAL.

Can you think of a different way to write this header using an output parameter instead of a result?

Step 2 is Designing the Program. It is here that we will often make use of our experience with similar or related problems to help in finding a solution. Have we ever written a program to solve a similar conversion problem - Fahrenheit to Centigrade, for instance, or converting a ratio of two integers into a real equivalent? What do we know of the relationship between the inputs and the output? Converting one currency to another is reasonably straightforward:


ConvertedAmount = Amount / ExchangeRate

We might also need to ask questions about special values that the inputs might have that require special treatment - this is often the case when solving problems involving lists, for instance, where the possibility of an empty list might need to be taken into account. There may be some values of the inputs for which the operation does not make sense. In this case we might need to be prepared to raise an exception or set the output to a special error-indicating value.

Step 3 is Writing Your Program. The design is fairly simple, so we can complete our procedure by putting the pieces together as follows:


Procedure ConvertToEcus(Amount, ExchangeRate : REAL) : REAL =
VAR
  ConvertedAmount : REAL;
BEGIN
  ConvertedAmount := Amount / ExchangeRate;
  RETURN ConvertedAmount;
END ConvertToEcus;

However, we need to be slightly careful because, although unlikely in practice, an exchange rate of zero will cause our program to fail. This is an example of a special input value to which we need to give special treatment. We really should test for this and raise an exception under these circumstances:


EXCEPTION ZeroExchangeRate;
Procedure ConvertToEcus(Amount, ExchangeRate : REAL) :
    REAL RAISES {ZeroExchangeRate} =
VAR
  ConvertedAmount : REAL;
BEGIN
  IF ExchangeRate = 0 THEN
    RAISE ZeroExchangeRate;
  END;
  ConvertedAmount := Amount / ExchangeRate;
  RETURN ConvertedAmount;
END ConvertToEcus;

Are there any other values of the exchange rate that do not make sense? If so, can you rewrite this example to take these into account?

Step 4 is Looking Back. This is very important because it causes you to evaluate what you have done and ask yourself whether there is anything that you might have done differently if you had to start all over again. It also allows you to focus on the lessons learned, and to add to your fund of experience for future problems that you will have to solve.

Here is another specification for you to try:

Write a procedure to convert an amount of money given in pounds and pence into the value in pence.

Can you use any of the lessons you learned from the previous problem in solving this one? Can you reuse any of the code, even?

To
Overview

To Next Page