Subsections

Routines

A routine is a number of encapsulated actions which can be elaborated in their entirety in other parts of the program. A routine has a well-defined mode. The value of a routine is expressed as a routine denotation. Here is an example:

   ([]INT a)INT:
   (
      INT sum:=0;
   
      FOR i FROM LWB a TO UPB a DO sum+:=i OD;
      sum
   )

In this example, the header of the routine is given by

   ([]INT a)INT:

which could be read as “with (parameter) row of INT a yielding INT”. The mode of the routine is given by the header, less the colon and any identifiers. So the mode of the above routine is

   ([]INT)INT

We say that the routine takes one parameter of mode []INT and yields a value of mode INT.

As you can see from the body of the routine (everything except the header), the routine yields the sum of the individual elements of the parameter. The body of a routine is a unit. In this case, it is an enclosed clause.

We have met parameters before in a different guise. The formal definition of an identity declaration is

<formal-mode-param > = < actual-mode-param >

The formal-mode-param consists of an identifier preceded by a formal-mode-declarer (referred to in the last chapter as a formal-declarer). An actual-mode-param is a piece of program which yields an internal object which henceforth is identified by the identifier. For example, in the identity declaration

   []INT a = (2,3,5,7,11)

[]INT a is the formal (mode) parameter, []INT is the formal (mode) declarer, the identifier is a, and the actual (mode) parameter is the row-display (2,3,5,7,11). The word “mode” was placed in parentheses because it is common usage to omit it. Henceforth, we shall talk about formal parameters and actual parameters.

In the header of the above routine, a is declared as a formal parameter. The mode of a is []INT. At the time the routine is declared, a does not identify a value. That is why it is called a “formal” parameter. It is only when the routine is used that a will identify a value. We'll come to that later. Any identifier may be used for the formal parameter of a routine.

In the body of the routine, a is treated as though it has a value. Since its mode is []INT, it is a multiple and so it can be sliced to access its individual elements.

The body of the routine written above consists of an enclosed clause. In this case, the enclosure consists of the parentheses ( and ), but it might well have been written using BEGIN and END. Inside the enclosure is a serial clause consisting of three phrases. The first is a declaration with an initial assignment. Although an assignment yields a name, an identity declaration with an initial assignment, even an abbreviated one, does not. This is the only exception.

The second phrase is a FOR loop clause which yields VOID (see section 6.1.4). The third phrase consists of the identifier sum which yields its name of mode REF INT.

Now, according to the header of the routine, the routine must yield a value of mode INT. The context of the body of a routine is strong. Although a serial clause cannot be coerced, the context of the serial clause is passed to the last phrase of that clause. In this case, we have a value of mode REF INT which, in a strong context, can be coerced to a value of mode INT by dereferencing.


Exercises

6.1
What is the formal definition of an identity declaration? Ans[*]
6.2
Why is the parameter of a routine denotation called a formal parameter? Ans[*]
6.3
In the routine denotation
   (REAL r)INT: ENTIER r;
Ans[*]
(a)
What is the mode of the formal parameter?

(b)
What is the mode of the value yielded?

(c)
What is the context of the body of the routine?

(d)
If the value of r were -4.6, what value would the routine yield?

6.4
Write a routine which takes a parameter of mode []INT and yields a value of mode []CHAR, where each element of the result yields the character equivalent of the corresponding element in the parameter (use FOR and REPR). Ans[*]


Routine modes

In general, a routine may have any number of parameters, including none, as we shall see. The mode of the parameters may be any mode, and the value yielded may be any mode. The modes written for the parameters and the yield are always formal declarers, so no bounds are used if the modes of the parameters or yield involve multiples.

Here is a possible header of a more complicated routine:

   (INT i,REF[,]CHAR c,REAL a,REAL b)BOOL:

A minor abbreviation would be possible in this case. The

   REAL a,REAL b

could be written REAL a,b giving

   (INT i,REF[,]CHAR c,REAL a,b)BOOL:

Notice that the parameters are separated by commas. This means that when the routine is used, the actual parameters are evaluated collaterally. We shall see later that this is important when we consider side-effects.

The order in which parameters are written in the header is of no particular significance.

The mode of the routine whose header is given above is

   (INT,REF[,]CHAR,REAL,REAL)BOOL

(“with int ref row row of car real real yielding bool”).

Multiples as parameters

Since a formal parameter which is a multiple has no bounds written in it, any multiple having that mode could be used as the actual parameter. This means that if you need to know the bounds of the actual multiple, you will need to use the bounds interrogation operators. For example, here is a routine denotation which finds the smallest element in its multiple parameter:

([]INT a)INT:
(
   INT min:=a[LWB a];

   FOR i FROM LWB a TO UPB a
   DO
       min:=min MIN a[i]
   OD;
   min
)

Names as parameters

The second parameter in the more complicated routine header given in section 6.1.1 had the mode REF[,]CHAR. When a parameter is a name, the body of the routine can have an assignment which makes the name refer to a new value. For example, here is a routine denotation which assigns a value to its parameter:

   (REF INT a)INT:  a:=2

Notice that the unit in this case is a single phrase and so does not need to be enclosed. Here is a routine denotation which has two parameters and which yields a value of mode BOOL:

   (REF[]CHAR rc,[]CHAR c)BOOL:
   IF UPB rc - LWB rc /= UPB c - LWB c
   THEN FALSE
   ELSE rc[:]:=c[:];  TRUE
   FI

Here, the body is a conditional clause which is another kind of enclosed clause. Note the use of trimmers to ensure that the bounds of the multiples on each side of the assignment match.

If a flexible name could be used as an actual parameter, then the mode of the formal parameter must include the mode constructor FLEX. For example,

   (REF FLEX[]CHAR s)INT:
   (CO Code to compute the number of words CO)

Of course, in this example, the mode of s could have been given as REF STRING.

The mode VOID

A routine must yield a value of some mode, but it is possible to throw away that value using the voiding coercion. The mode VOID has a single value whose denotation is EMPTY. In practice, because the context of the yield of a routine is strong, use of EMPTY is usually unnecessary (but see section 8.2). Here is another way of writing the last routine in the previous section:

   (REF[]CHAR rc,[]CHAR c)VOID:
   IF UPB rc - LWB rc /= UPB c - LWB c
   THEN
      print(("Bounds mismatch",newline));
      stop
   ELSE rc[:]:=c[:]
   FI

This version produces an emergency error message and terminates the program prematurely (see section 4 for details of stop). Since the yield is VOID, any value the conditional clause might yield will be thrown away. A FOR loop yields EMPTY and a semicolon voids the previous unit. Declarations yield no value, not even EMPTY.

Routines yielding names

Since the yield of a routine can be a value of any mode, a routine can yield a name, but there is a restriction: the name yielded must have a scope larger than the body of the routine. This means that any names declared to be local, cannot be passed from the routine. Names which exist outwith the scope of the routine can be passed via a parameter and yielded by the routine. For example, here is a routine denotation which yields the name passed by such a parameter:

   (REF INT a)REF INT:  a:=2

Compare this routine with the first routine denotation in section 6.1.3.

In chapter 5, we said that a new name can be declared using the generator LOC. For example, here is an identity declaration for a name:

   REF INT x = LOC INT

The range of the identifier x is the smallest enclosed clause in which it has been declared. The scope of the value it identifies is limited to that smallest enclosed clause because the generator used is the local generator LOC. Here is a routine which tries to yield a name declared within its body:

   (INT a)REF INT:
     (REF INT x = LOC INT:=a;  x) #wrong!#

This routine is wrong because the scope of the name identified by x is limited to the body of the routine. Note, however, the a68toc Algol 68 compiler provides neither compile-time nor run-time scope checking so that it is possible to yield a locally declared name. However, the rest of the program would be undefined--you might or might not get meaningful things happening. When scopes are checked, this sort of error cannot occur.

However, there is a way of yielding a name declared in a routine. This is achieved using a global generator. If x above were declared as

   REF INT x = HEAP INT

or, in abbreviated form, HEAP INT x, then the scope of the name identified by x would be from its declaration to the end of the program even though the range of the identifier x is limited to the body of the routine:

   (INT a)REF INT:  (HEAP INT x:=a;  x)

Notice that the mode of the yield is still REF INT. All that has changed is the scope of the value yielded. Of course, you would not be able to identify the yielded name using x, but we'll come to that problem when we deal with how routines are used. Notice that the global generator is written HEAP instead of GLOB as you might expect. This is because global generators use a different method of allocating storage for names with global scope and, historically, this different method is recorded in the mode constructor.

The difference between range and scope is that identifiers have range, but values have scope. Furthermore, denotations have global scope.


Exercises

6.5
Write the header of a routine with a parameter of mode REF REAL and which yields a value of mode REAL. Ans[*]
6.6
Write the header of a routine which takes two parameters each of which is a name of mode REF CHAR, and yields a name of mode REF CHAR. Ans[*]
6.7
Write a routine which takes a parameter of mode STRING and yields a value of mode []STRING consisting of the words of the parameter (use your answer to exercise A). Ans[*]

Parameterless routines

A routine can have no parameters. In the header, the parentheses normally enclosing the formal parameter list (either one parameter, or more than one separated by commas) are also omitted. Here is a routine with no parameters and which yields a value of mode INT:

   INT: 2*3**4 - ENTIER 36.5

It would be more usual to use identifiers which had been declared in some enclosing range. For example,

   INT: 2*a**4 - ENTIER b

Routines which have no parameters and yield no value are fairly common. For example,

   VOID:  print(2)

Strictly speaking, there is one value having the mode VOID, but there's not a lot you can do with it. In practice, VOID routines usually use identifiers declared in an enclosing range (they are called identifiers global to the routine). For example:

   VOID: (x:=a;  x<=2|print(x)|print("Over 2"))

where the body is an abbreviated conditional clause, and x and a have been declared globally with appropriate modes.

Assignment of values to names declared globally7.1 to the routine is known as a side-effect. We shall deal with side-effects when we describe how routines are used, and we shall show why side-effects are undesirable. If you write parameterless routines, it is preferable not to put any assignments to globally-declared names in them. In fact, it would be safer to say: “In a routine, don't assign to names not declared in the routine or not provided as parameters”. Side-effects are messy and are usually a sign of badly designed programs. It is better to use a parameter (or an extra parameter) using a name, and then assign to that name. This ensures that values can only get into or out of your routines via the header, and results in a much cleaner design. Cleanly designed programs are easier to write and easier to maintain.


Exercises

6.8
Write the header of a routine which yields a value of mode REAL, but takes no parameters. Ans[*]
6.9
Write a routine of mode VOID which prints
   Hi, there
on your screen. Ans[*]


Sian Mountbatten 2012-01-19