|The Trick Of The Mind - Modular Programming|
|Written by Mike James|
|Tuesday, 05 September 2023|
Page 2 of 3
Notice that there are two aspects of a subroutine, its definition and its use. It takes time to avoid being confused by the use of its name in the definition and in its use. We can increase the flexibility of subroutines by including ways to customize what they do by introducing parameters to the definition. A parameter is another example of something that we use as a stand-in during definition for an actual value that is supplied at use. For example:
Subroutine MakeCoffee(Sugar) get cup dispense coffee into cup If Sugar > 0 Then add sugar stir Return
It is an almost universal convention that parameters are written after the name of the subroutine. In this case Sugar is supposed to be supplied as a value that indicates the number of spoonfuls needed. If greater than zero we add sugar and stir. We will look at how to get the right amount of sugar in a moment.
With this new definition of MakeCoffee we can now use it with a specification for the parameter Sugar:
Sugar = 10 MakeCoffee(Sugar)
In both cases Sugar is set to 10 before MakeCoffee starts. Strictly we call the value that we set a parameter to an “argument” but most people aren’t that strict and also call it a parameter.
This is relatively simple but there is a subtle point to lookout for. Because things are generally named to be meaningful you often end up writing instructions like:
Sugar = 2 MakeCoffee(Sugar)
and it is tempting to think that the variable Sugar used as the argument is the same variable as Sugar used as the parameter. They are completely separate and have nothing to do with one another apart from having the same name. What this means is that it is a convention that you can use the variable name Sugar within the subroutine without worrying about what things are called in the rest of the program. This brings us to the second big advantage of subroutines – isolation from the rest of the program. You can write a subroutine without worrying about the rest of the program and you can make the connection between the two only via the parameters. This is an example of encapsulation which is something we will return to when we meet objects in Chapter 12.
Return Values and Functions
Parameters are a way of getting values into a subroutine – what about getting results out? This is one of the most difficult areas and there is no general agreement on what is best in all cases, but there are easy to understand considerations.
The simplest case is when a subroutine needs to return a single value only. In this case the subroutine is generally called a function. That is, a function is a subroutine that returns a single value.
For example, the Max function will return the largest of two numbers:
Subroutine Max(a,b) If a > b Then m = a Else m = b
you should be able to see that the variable m is set to the larger of the two numbers, but how do we get the result out of the subroutine so that it can be used in the rest of the program? There are a number of conventions how to do this, but the most common is to allow a return value to be specified – i.e. a value is specified as part of the return instruction. For example:
Function Max(a,b) If a > b Then m = a Else m = b Return m
now the value that the subroutine returns is whatever m turns out to be when it is run. Subroutines that return a single value are called functions and this is reflected in the change in the first line.
How does the program that called the function get the value that is returned? The answer is that the function name is treated as if it was a variable that stored the return value, for example:
answer = Max(10,3)
This calls the function which works out the maximum of the two values and returns the result in stored in m. This is then stored in answer as if Max was a variable that contained the result. The end result is that 10 is stored in answer.
The huge advantage of this approach is that functions can be used within arithmetic expressions. For example:
answer = 4 * Max(10,3) + 2
stores 42 in answer.
This may seem like a small thing, but it opens the door to a world of sophistication. Now you can get a lot of your computing done just by writing arithmetic expressions that include functions. For example:
Bid = BestBid(CurrentBid) + 5
gives the next bid in an auction with a safety margin of 5. Of course, it all hinges on the BestBid function being able to work out what the best bid actually is, but the principle is good.
This may not seem like much, but notice that a function can contain both loops and conditionals. This boosts the Arithmetic little language to a full Turing-complete language. Yes, you can compute anything that can be computed with an arithmetic expression as long as you can use functions which in turn are written in a Turing-complete language.
Expressions, because now they are more than “arithmetic” expressions, are so attractive that there is a whole branch of programming that advocates that you should use nothing but functions and expressions – functional programming. This philosophy has a lot going for it, but as with many philosophies, puritans taken with the idea push it to the point where anything that isn’t “functional” should not be used. Strict functional programming aims to make programming identical to mathematics. In particular, functions should always return the same output given the same input. This leads on to a philosophy of immutability where nothing can change. While a lot of these ideas work well when applied in a relaxed way, they quickly become unmanageable for the average programmer confronted with complex ideas from category theory. Functions are a powerful way to do some tasks, but taken too far the idea damages the things that makes programming different from mathematics – i.e. that it is dynamic.
|Last Updated ( Tuesday, 05 September 2023 )|