Functions and procedures



When programs grow larger than the small examples presented so far, it becomes necessary to introduce some structure that makes them easier to read and to maintain. Usually, this is done by dividing the tasks that have to be executed into subtasks which may again be subdivided, and indicating the order in which these subtasks have to be executed and which are their activation conditions. To facilitate this structured approach, Mosel provides the concept of subroutines. Using subroutines, longer and more complex programs can be broken down into smaller subtasks that are easier to understand and to work with. Subroutines may be employed in the form of procedures or functions. Procedures are called as a program statement, they have no return value, functions must be called in an expression that uses their return value.

Mosel provides a set of predefined subroutines (for a comprehensive documentation the reader is referred to the Mosel Reference Manual), and it is possible to define new functions and procedures according to the needs of a specific program. A procedure that has occured repeatedly in this document is writeln. Typical examples of functions are mathematical functions like abs, floor, ln, sin etc.

Subroutine definition

User defined subroutines in Mosel have to be marked with procedure / end-procedure and function / end-function respectively. The return value of a function has to be assigned to returned as shown in the following example (model subrout.mos).

model "Simple subroutines"

 declarations
  a:integer
 end-declarations

 function three:integer
  returned := 3
 end-function

 procedure print_start
  writeln("The program starts here.")
 end-procedure

 print_start 
 a:=three
 writeln("a = ", a)

end-model

This program will produce the following output:

The program starts here.
a = 3

Parameters

In many cases, the actions to be performed by a procedure or the return value expected from a function depend on the current value of one or several objects in the calling program. It is therefore possible to pass parameters into a subroutine. The (list of) parameter(s) is added in parantheses behind the name of the subroutine:

 function times_two(b:integer):integer
  returned := 2*b
 end-function 

The structure of subroutines being very similar to the one of model, they may also include declarations sections for declaring local parameters that are only valid in the corresponding subroutine. It should be noted that such local parameters may mask global parameters within the scope of a subroutine, but they have no effect on the definition of the global parameter outside of the subroutine as is shown below in the extension of the example `Simple subroutines'. Whilst it is not possible to modify function/procedure parameters in the corresponding subroutine, as in other programming languages the declaration of local parameters may hide these parameters. Mosel considers this as a possible mistake and prints a warning during compilation (without any consequence for the execution of the program).

model "Simple subroutines"

 declarations
  a:integer
 end-declarations

 function three:integer
  returned := 3
 end-function

 function times_two(b:integer):integer
  returned := 2*b
 end-function

 procedure print_start
  writeln("The program starts here.")
 end-procedure

 procedure hide_a_1
  declarations
   a: integer
  end-declarations
 
  a:=7
  writeln("Procedure hide_a_1: a = ", a) 
 end-procedure

 procedure hide_a_2(a:integer)
  writeln("Procedure hide_a_2: a = ", a) 
 end-procedure

 procedure hide_a_3(a:integer)
  declarations
   a: integer
  end-declarations

 a := 15
  writeln("Procedure hide_a_3: a = ", a) 
 end-procedure

 print_start 
 a:=three
 writeln("a = ", a)
 a:=times_two(a)   
 writeln("a = ", a)
 hide_a_1
 writeln("a = ", a)
 hide_a_2(-10)
 writeln("a = ", a)
 hide_a_3(a)
 writeln("a = ", a)

end-model 

During the compilation we get the warning

Mosel: W-165 at (30,3) of `subrout.mos': Declaration of `a' hides a parameter.

This is due to the redefinition of the parameter in procedure hide_a_3. The program results in the following output:

The program starts here.
a = 3
a = 6
Procedure hide_a_1: a = 7
a = 6
Procedure hide_a_2: a = -10
a = 6
Procedure hide_a_3: a = 15
a = 6

Recursion

The following example (model lcdiv2.mos) returns the largest common divisor of two numbers, just like the example `Lcdiv1' in the previous chapter. This time we implement this task using recursive function calls, that is, from within function lcdiv we call again function lcdiv.

model Lcdiv2

 function lcdiv(A,B:integer):integer
  if(A=B) then
   returned:=A
  elif(A>B) then
   returned:=lcdiv(B,A-B)
  else
   returned:=lcdiv(A,B-A)
  end-if   
 end-function

 declarations
  A,B: integer
 end-declarations

 write("Enter two integer numbers:\n  A: ")
 readln(A)
 write("  B: ")
 readln(B)

 writeln("Largest common divisor: ", lcdiv(A,B))

end-model

This example uses a simple recursion (a subroutine calling itself). In Mosel, it is also possible to use cross-recursion, that is, subroutine A calls subroutine B which again calls A. The only pre-requisite is that any subroutine that is called prior to its definition must be declared before it is called by using the forward statement (see below).

forward

A subroutine has to be `known' at the place where it is called in a program. In the preceding examples we have defined all subroutines at the start of the programs but this may not always be feasible or desirable. Mosel therefore enables the user to declare a subroutine seperately from its definition by using the keyword forward. The declaration of of a subroutine states its name, the parameters (type and name) and, in the case of a function, the type of the return value. The definition that must follow later in the program contains the body of the subroutine, that is, the actions to be executed by the subroutine.

The following example (model qsort1.mos) implements a quick sort algorithm for sorting a randomly generated array of numbers into ascending order. The procedure qsort that starts the sorting algorithm is defined at the very end of the program, it therefore needs to be declared at the beginning, before it is called. Procedure qsort_start calls the main sorting routine, qsort. Since the definition of this procedure precedes the place where it is called there is no need to declare it (but it still could be done). Procedure qsort calls yet again another subroutine, swap.

The idea of the quick sort algorithm is to partition the array that is to be sorted into two parts. The `left' part containing all values smaller than the partitioning value and the `right' part all the values that are larger than this value. The partitioning is then applied to the two subarrays, and so on, until all values are sorted.

model "Quick sort 1"

 parameters
  LIM=50
 end-parameters
 
 forward procedure qsort_start(L:array(range) of integer)

 declarations
  T:array(1..LIM) of integer
 end-declarations
 
 forall(i in 1..LIM) T(i):=round(.5+random*LIM)
 writeln(T)
 qsort_start(T)
 writeln(T) 

! Swap the positions of two numbers in an array 
 procedure swap(L:array(range) of integer,i,j:integer)
  k:=L(i)
  L(i):=L(j)
  L(j):=k
 end-procedure

! Main sorting routine
 procedure qsort(L:array(range) of integer,s,e:integer)
  v:=L((s+e) div 2)              ! Determine the partitioning value
  i:=s; j:=e
  repeat                         ! Partition into two subarrays
   while(L(i)<v) i+=1
   while(L(j)>v) j-=1
   if i<j  then
    swap(L,i,j)
    i+=1; j-=1
   end-if
  until i>=j
                                 ! Recursively sort the two subarrays
  if j<e and s<j then qsort(L,s,j); end-if
  if i>s and i<e then qsort(L,i,e); end-if
 end-procedure

! Start of the sorting process 
 procedure qsort_start(L:array(r:range) of integer)
  qsort(L,getfirst(r),getlast(r))
 end-procedure

end-model

The quick sort example above demonstrates typical uses of subroutines, namely grouping actions that are executed repeatedly (qsort) and isolating subtasks (swap) in order to structure a program and increase its readability.

The calls to the procedures in this example are nested (procedure swap is called from qsort which is called from qsort_start): in Mosel there is no limit as to the number of nested calls to subroutines (it is not possible, though, to define subroutines within a subroutine).

Overloading of subroutines

In Mosel, it is possible to re-use the names of subroutines, provided that every version has a different number and/or types of parameters. This functionality is commonly referred to as overloading.

An example of an overloaded function in Mosel is getsol: if a variable is passed as a parameter it returns its solution value, if the parameter is a constraint the function returns the evaluation of the corresponding linear expression using the current solution.

Function abs (for obtaining the absolute value of a number) has different return types depending on the type of the input parameter: if an integer is input it returns an integer value, if it is called with a real value as input parameter it returns a real.

Function getcoeff is an example of a function that takes different numbers of parameters: if called with a single parameter (of type linctr) it returns the constant term of the input constraint, if a constraint and a variable are passed as parameters it returns the coefficient of the variable in the given constraint.

The user may define (additional) overloaded versions of any subroutines defined by Mosel as well as for his own functions and procedures. Note that it is not possible to overload a function with a procedure and vice versa.

Using the possibility to overload subroutines, we may rewrite the preceding example `Quick sort' as follows (model qsort2.mos).

model "Quick sort 2"

 parameters
  LIM=50
 end-parameters
                             
 forward procedure qsort(L:array(range) of integer)

 declarations
  T:array(1..LIM) of integer
 end-declarations
 
 forall(i in 1..LIM) T(i):=round(.5+random*LIM)
 writeln(T)
 qsort(T)
 writeln(T) 

 procedure swap(L:array(range) of integer,i,j:integer)
  (...)                        (same procedure body as in the preceding example)
 end-procedure

 procedure qsort(L:array(range) of integer,s,e:integer)
  (...)                        (same procedure body as in the preceding example)
 end-procedure

! Start of the sorting process 
 procedure qsort(L:array(r:range) of integer)
  qsort(L,getfirst(r),getlast(r))
 end-procedure

end-model

The procedure qsort_start is now also called qsort. The procedure bearing this name in the first implementation keeps its name too; it has got two additional parameters which suffice to ensure that the right version of the procedure is called. To the contrary, it is not possible to give procedure swap the same name qsort because it takes exactly the same parameters as the original procedure qsort and hence it would not be possible to differentiate between these two procedures any more.



If you have any comments or suggestions about these pages, please send mail to docs@dashoptimization.com.