Subsections

- Parameterless procedures
- Exercises
- Procedures with parameters
- Exercises
- Procedures as parameters
- Exercises
- Recursion
- Exercises
- Standard procedures
- Other features of procedures

# Procedures

The second way of using routines is to declare them as procedures. We have seen that an operator can be declared and used, have a mode and a value (its routine denotation), but apart from having an operator symbol, it cannot be identified with an identifier in the way that a name or a denotation of a `CHAR` value can. Procedures are quite different.

Firstly, here are some general remarks on the way procedures differ from operators. The mode of a procedure always starts with the mode constructor `PROC`. A procedure can have any number of parameters, including none. Two procedures having the same identifier cannot be declared in the same range (so “overloading” is not allowed). When a procedure is used, its parameters, if any, are in a strong context. This means that rowing and widening are available.

Procedures are declared using the mode constructor `PROC`. Here is a procedure which creates a range of characters:

       PROC(CHAR,CHAR)[]CHAR range =
          (CHAR a,b)[]CHAR:
          BEGIN
             CHAR aa,bb;

             (a<=b|aa:=a; bb:=b|aa:=b; bb:=a);

             [ABS aa:ABS bb]CHAR r;

             FOR i
             FROM LWB r TO UPB r
             DO
                r[i]:=REPR i
             OD;
             r
          END

This procedure identity declaration resembles the declaration for a multiple: much of the mode is repeated on the right-hand side and the formal-declarer on the left-hand side has no identifiers for the modes of the parameters. Notice that the modes of the parameters must be repeated in the formal-declarer, but that the mode of the procedure on the right-hand side can contain the usual abbreviation. Here is the abbreviated header:

       PROC range = (CHAR a,b)[]CHAR:

The formal-declarer is important for creating synonyms:

      PROC(REAL)REAL sine = sin

Two or more procedure declarations can be separated by commas, even if the procedures have different modes. Consider, for example:

       PROC pa = (INT i)INT:  i*i,
            pb = (INT i)CHAR: REPR(i*i),
            pc = (INT i)REAL: (i=0|0|1/i)

## Parameterless procedures

Procedures can have no parameters. Suppose the following names have been declared:

       INT i,j

Here is a procedure with mode `PROC INT` which yields an `INT`:

       PROC INT p1 = INT: i:=3+j

A procedure can be invoked or called by writing its identifier. For example, the procedure `p1` would be called by

       p1

or

       INT a = p1

The right-hand side of this identity declaration requires a value of mode `INT`, but it has been given a unit of mode `PROC INT`. This is converted into a value of mode `INT` by the coercion known as deproceduring. This coercion is available in every context (even soft).

Have you realised that `print` must be the identifier of a procedure? Well done! However, we cannot talk about its parameters yet because we don't know enough about the language.

Here is another procedure which yields a name of mode `REF INT`. The mode of the procedure is `PROC REF INT`:

       PROC p2 = REF INT: IF i < 0 THEN i ELSE j FI

and assumes that the names identified by `i` and `j` had already been declared. Here is an identity declaration which uses `p2`:

       REF INT i or j = p2

Because `p2` yields a name, it can be used on the left-hand side of an assignment:

       p2:=4

Here, `4` will be assigned to `i` or `j` depending on the value `i` refers to. The left-hand side of an assignment has a soft context in which only the deproceduring coercion is allowed.

In procedures `p1` and `p2`, the identifier `i` had been declared globally to the procedures. Assignment to such an identifier is, as already stated, a side-effect. Here is another procedure of mode `PROC INT` which uses a global identifier, but does not assign to it:

       PROC p3 = REAL:
       (
          [i]REAL a;  read((a,newline));
          REAL sum:=0.0;

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

          sum
       )

and here is a call of `p3`:

       print(p3)

In the identity declaration

       REAL r = p2

`p2` is deprocedured to yield a name of mode `REF INT`, dereferenced to yield an `INT`, and then widened to yield a `REAL`. All these coercions are available in a strong context (the right-hand side of an identity declaration).

The call of a procedure can appear in a formula without parentheses. Here is an example:

       p2:=p1 * ROUND p3

If we call the procedure `p1`, declared above, its value does not have to be used. For example, in

       p1;

the value yielded by `p1` has been voided by the following semicolon after the procedure had been called.

In the section on routines, we introduced the mode `VOID`. Here is a procedure yielding `VOID`:

       PROC p4 = VOID:  print(p3)

and a possible use:

       ; p4;

where the semicolons show that the call stands on its own.

When a parameterless procedure yields a multiple, the call of that procedure can be sliced to get an individual element. For example, suppose we declare

       PROC p5 = [,]REAL:
       (
          [i,j]REAL a;
          read((a,newline));
          a
       )

where `i` and `j` were declared above, and then call `p5` in the formula

       REAL x = p5[i-3,j] * 2

When `p5` is called, it yields a two-dimensional multiple of mode `[,]REAL` which is then sliced using the two subscripts (assuming that `i-3` is within the bounds of the first dimension) to yield a value of mode `REAL`, which is then used in the formula.

Procedure `p2`, declared above, yielded a name declared globally to the procedure. As explained in the sections on routines, a procedure cannot yield a locally-generated name. However, if the name is generated using `HEAP`, then the name can be yielded as in `p6`:

       PROC p6 = REF INT: (HEAP INT i:=3;  i)

Here is a call of `p6` where the yielded name is captured with an identity declaration:

       REF INT global int = p6

Then `print(global int)` will display `3`.

The yield of a procedure can be another procedure. Consider this program fragment:

       PROC q2 = INT: max int % 2,
            q3 = INT: max int % 3,
            q4 = INT: max int % 4,
            q5 = INT: max int % 5;

       INT i;  read((i,newline));

       PROC q = PROC INT:
          CASE i+1 IN q2,q3,q4 OUT q5 ESAC

Procedure `q` will yield one of the predeclared procedures depending on the value of `i`. Here, the yielded procedure will not be deprocedured because the mode required is a procedure.

One parameterless procedure is provided in the standard prelude. Its identifier is `random`, and when called returns the next pseudo-random real number of a series. If called a large number of times, the numbers yielded are uniformly distributed in the range [0,1].

## Exercises

6.19
  Write a procedure which assigns a value to a name declared globally to the procedure. Ans
6.20
  Write a procedure which reads an integer from the keyboard, then declares a dynamic name of a multiple of one dimension, and reads that number of integers from the keyboard. Now compute the sum of all the integers, and yield its value as the yield of the procedure. Ans
6.21
  Write a procedure which yields the name of a two dimensional multiple containing characters read from the keyboard. The mode of the multiple should be `REF[,]CHAR`. Ans

## Procedures with parameters

Parameters of procedures can have any mode (including procedures). Unlike operators, procedures can have any number of parameters. The parameters are written as a parameter list which consists of one parameter, or two or more separated by commas.

Here is a procedure with a single parameter:

       PROC(INT)CHAR p7 = (INT i)CHAR: REPR(i>0|i|0)

This is a full identity declaration for `p7`. It can be abbreviated to

       PROC p7 = (INT i)CHAR: REPR(i>0|i|0)

The mode of `p7` is `PROC(INT)CHAR`. That is, `p7` is a procedure with a single integer parameter and yielding a character. Here is a call of `p7`:

       CHAR c = p7(-3)

Note that the single parameter is written between parentheses. Since the context of an actual parameter of a procedure is strong, a name of mode `REF INT` could be used:

       CHAR c = p7(i)

or

       CHAR c = p7(ai[j])

where `ai` has mode `REF[]INT` and `j` has mode `INT` or `REF INT` or `PROC INT` (or even `PROC REF INT`).

Here is a procedure which takes three parameters:

       PROC char in string =
          (CHAR c,REF INT p,STRING s)BOOL:
       (
          BOOL found:= FALSE;
          FOR k FROM LWB s TO UPB s
          WHILE NOT found
          DO
             (c = s[k] | i:=k; found:= TRUE)
          OD;
          found
       )

The procedure (which is in the standard prelude) tests whether a character is in a string, and if it is, returns its position in the parameter `p`. The procedure yields `TRUE` if the character is in the string, and `FALSE` if not. Here is a possible call of the procedure:

       IF INT p;  char in string(char,p,"abcde")
       THEN ...

where `char` was declared in an outer range. Notice that the `REF INT` parameter of `char in string` is not assigned a new value if the character is not found in the string.

When calling a procedure, the call must supply the same number of actual parameters, and in the same order, as there are formal parameters in the procedure declaration.

If a multiple is one of the formal parameters, a row-display can be supplied as an actual parameter (remember that a row-display can only occur in a strong context). In this case, the row-display counts as a single parameter, but the number of elements in the row-display can differ in successive calls since the bounds of the multiple can be determined by the procedure using the bounds interrogation operators. Here is an example:

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

and here are some calls of `pb`:

       pb((1,2,3))   pb((2,3,5,7,11,13))

Again, procedures with parameters can assign to, or use, globally declared names and other values, but it is better to include the name in the header of the procedure. Here is a procedure which reads data into a globally declared multiple using that multiple as a parameter:

       PROC rm = (REF[]REAL a)VOID:
          read((a,newline))

It could now be called by

       rm(multiple)

where `multiple` had been previously declared as having mode `REF[]REAL`.

As described in section 6.1.3, a flexible name can be used as an actual parameter provided that the formal parameter has also been declared as being flexible. For example, here is a procedure which takes a single parameter of mode `REF STRING` and which yields an `INT`:

       PROC read line = (REF STRING s)INT:
       (
          read((s,newline));
          UPB s #LWB is 1#
       )

`read line` reads the next line of characters from the keyboard, assigns it to its parameter, which is a flexible name, and yields the length of the line.

## Exercises

6.22
  Write a procedure which takes a `REF REAL` parameter, divides the value it refers to by , multiplies it by 180, assigns the final value to its parameter, and yields the parameter (that is, its name). Ans
6.23
  Write a procedure which takes two parameters: the first should have mode `STRING` and the second mode `INT`. Display the string on the screen the number of times given by the integer. If the integer is negative, display a newline first and then use the absolute value (use the operator `ABS`) of the integer. Yield the mode `VOID`. Ans
6.24
  Write a procedure, identified as `num in multiple`, which does for an integer what `char in string` does for a character. Ans

## Procedures as parameters

Here is a procedure which takes a procedure as a parameter:

       PROC sum = (INT n,PROC(INT)REAL p)REAL:
       (
          REAL s:=0;
          FOR i TO n DO s+:=p(i) OD;
          s
       )

Notice that the mode of the procedure parameter is a formal mode so no identifier is required for its `INT` parameter in the header of the procedure `sum`. In the loop clause, the procedure is called with an actual parameter.

When a parameter must be a procedure, there are two ways in which it can be supplied. Firstly, a predeclared procedure identifier can be supplied, as in

       PROC pa = (INT a)REAL: 1/a;
       sum(34,pa)

Secondly, a routine denotation can be supplied:

       sum(34,(INT a)REAL: 1/a)

A routine denotation is a unit. In this case, the routine denotation has the mode `PROC(INT)REAL`, so it can be used in the call of `sum`. Notice also that, because the routine denotation is an actual parameter, its header includes the identifier `a`. In fact, routine denotations can be used wherever a procedure is required, so long as the denotation has the required mode. The routine denotation given in the call is on the right-hand side of the implied identity declaration of the elaboration of the parameter. It is an example of an anonymous routine denotation.

## Exercises

6.25
  Given the declaration of `sum` in the text, what is the value of: Ans
  (a)
    `sum(4,(INT a)REAL: a)`

  (b)
    `sum(2,(INT b)REAL: 1/(5*b))`

  (c)
    `sum(0,pa)` (`pa` is declared in the text)

## Recursion

One of the fun aspects of using procedures is that a procedure can call itself. This is known as recursion. For example, here is a simplistic way of calculating a factorial:

       PROC factorial = (INT n)INT:
          (n=1|1|n*factorial(n-1))

Try it with the call

       factorial(7)

Here is another recursively defined procedure which displays an integer on the screen in minimum space:

       PROC ai = (INT i)VOID:
       IF   i < 0  THEN print("-");  ai(ABS i)
       ELIF i < 10 THEN print(REPR(i+ABS"0"))
       ELSE ai(i%10);  ai(i MOD 10)
       FI

In each of these two cases, the procedure includes a test which chooses between a recursive call and phrases which do not result in a recursive call. This is necessary because, otherwise, the procedure would never complete. Neither of these procedures uses a locally declared value. Here is one which does:

       PROC new fact = (INT i)INT:
       IF INT n:=i-1;   n = 1
       THEN 2
       ELSE i*new fact(n)
       FI

The example is somewhat artificial, but illustrates the point. If `new fact` is called by, for example, `new fact(3)`, then in the first call, `n` will have the value `2`, and `new fact` will be called again with the parameter equal to `2`. In the second call, `n` will be `1`, but this `n` this time round will be a new `n`, with the first `n` inaccessible (it being declared in an enclosing range). `new fact` will yield `2`, and this value will be used in the formula on line 4 of the procedure. The first call to `new fact` will then exit with the value `6`.

Apart from being fun, recursive procedures can be an efficient way of programming a particular problem. Chapter 11 deals with, amongst other topics, recursive modes, and there, recursive programming comes into its own.

A different form of recursion, known as mutual recursion, is exemplified by two procedures which call each other. You have to ensure there is no circularity. The principal difficulty of how to use a procedure before it has been declared is overcome by first declaring a procedure name and then assigning a routine denotation to the procedure name after the other procedure has been declared. Here is a simple example:7.3

       PROC(INT)INT pb;
       PROC pa = (INT i)INT: (i>0|pb(i-1)|i);
       pb:=(INT i)INT: (i<0|pa(i+1)|i);

Then `pa(4)` would yield `3` and `pa(-4)` would yield `-4`. Similarly, `pb(4)` would yield `4` and `pb(-4)` would yield `-3`. Notice that the right-hand side of the assignment is an anonymous routine denotation.

## Exercises

6.26
  Write a recursive procedure to reverse the order of letters in a value of mode `[]CHAR`. It should yield a value also of mode `[]CHAR`. Ans
6.27
  Write two mutually recursive procedures which take an integer parameter and which yield an `INT`. The first should call the second if the parameter is odd, and the second should call the first if the parameter is even. The alternative option should yield the square of the parameter for the first procedure and the cube of the parameter for the second procedure. Use `square` and `cube` as the procedure identifiers. Ans

## Standard procedures

The standard prelude contains the declarations of more than 60 procedures, most of them concerned with transput (see chapter 9). A number of procedures, all having the mode

       PROC(REAL)REAL

are declared in the standard prelude and yield the values of common mathematical functions. These are `sqrt`, `exp`, `ln`, `cos`, `sin`, `tan`, `arctan`, `arcsin` and `arccos`. Naturally, you must be careful to ensure that the actual parameter for `sqrt` is non-negative, and that the actual parameter for `ln` is greater than zero. The procedures `cos`, `sin` and `tan` expect their `REAL` parameter to be in radians.

New procedures using these predeclared procedures can be declared:

       PROC sinh =
          (REAL x)REAL: (exp(x) + exp(-x))/2

A variety of pseudo-random numbers can be produced using `random int`. The mode of the procedure `random int` is

       PROC(INT)INT

and yields a pseudo-random integer greater than or equal to one, and less than or equal to its integer parameter. For example, here is a procedure which will compute the percentage of each possible die throw in 10 000 such throws:

       PROC percentage = []REAL:
       (
          PROC throw = INT: random int(6);

          [6]REAL result:=(0,0,0,0,0,0);

          TO 10 000 DO result[throw]+:=1 OD;

          FOR i FROM LWB result TO UPB result
          DO result[i] /:= 10 000 OD;

          result
       )

Notice that `percentage` has another procedure (`throw`) declared within it. There is no limit to such nesting.

## Other features of procedures

Since a procedure is a value, it is possible to declare values whose modes include a procedure mode. For example, here is a multiple of procedures:

       []PROC(REAL)REAL pr = (sin,cos,tan)

and here is a possible call:

       pr[2](2)

which yields -0⋅416 146 836 5. We could also declare a procedure which could be called with the expression

       pr(2)[2]

but this is left as an exercise.

Similarly, names of procedures can be declared and can be quite useful. Instead of declaring

       PROC pc = (INT i)PROC(REAL)REAL: pr[i]

using `pr` declared above, with a possible call of `pc(2)` we could write

       PROC(REAL)REAL pn:=pr[i]

and then use `pn` instead of `pc`. The advantage of this would be that `pr` would be subscripted only once instead of whenever `pc` is elaborated. Furthermore, another procedure could be assigned to `pn` and the procedure it refers to again called. Using `pn` would usually involve dereferencing.

There are scoping problems involved with procedure names. Although the scope of a denotation is global, procedure denotations may include an identifier whose range is not global. For this reason, the scope of a procedure denotation is limited to the smallest enclosing clause containing a declaration of an identifier or mode or operator indicant which is used in the procedure denotation.

For example, in this program fragment

       PROC REAL pp;  REAL y;
       BEGIN
          REAL x:=3.0;
          PROC p = REAL:  x:=4.0;
          print(p);
          pp:=p; CO wrong CO
          print(x)
       END;
       print(("pp=",pp)) #wrong#

the assignment in line 6 is wrong because the scope of the right-hand side is less than the scope of the left-hand side. Unfortunately, the a68toc compiler does not perform scope checking and so will not flag the incorrect assignment.

There are times when `SKIP` is useful in a procedure declaration:

       PROC p = REAL:
       IF x<0
       THEN print("Negative parameter"); stop; SKIP
       ELSE sqrt(x)
       FI

The yield of the procedure is `REAL`, so each part of the conditional clause must yield a value of mode `REAL`. The construct `stop` yields `VOID`, and even in a strong context, `VOID` cannot be coerced to `REAL`. However, `SKIP` will yield an undefined value of any required mode. In this case, `SKIP` yields a value of mode `REAL`, but the value is never used, because the program is terminated just before.

Grouping your program into procedures helps to keep the logic simple at each level. Nesting procedures makes sense when the nested procedures are used only within the outer procedures. This topic is covered in greater depth in chapter 12.