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
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.
(REAL r)INT: ENTIER r;Ans
r
were -4.6
, what value
would the routine yield?
[]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
). AnsIn 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”).
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 )
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
.
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
.
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.
REF REAL
and which yields a value of mode REAL
.
AnsREF CHAR
, and yields a name of mode
REF CHAR
. AnsSTRING
and
yields a value of mode []STRING
consisting of the words of the
parameter (use your answer to exercise A).
AnsA 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.
REAL
, but takes no parameters. AnsVOID
which prints
Hi, thereon your screen. Ans
Sian Mountbatten 2012-01-19