next up previous contents
Next: Resources and Globals Up: The MPD Programming Language Previous: Sequential Control   Contents

Subsections

Operations, Procedures and Processes

At the heart of the MPD language is the notion of operations. Operations are part of both the sequential part of MPD and the concurrent part depending on their use.

One use of operations is as dynamic (possibly two-way) channels on which messages can be sent and received. This mode of operation arises from the use of an in statement at the receiver. This is strictly part of concurrent MPD and will not be discussed here.

The other possible use of operations is to implement it with a proc. In short a proc provides an implementation for the operation in the form of a block of program code which gets run every time the operation is invoked. As we shall see, the combination of operations and procs is used to encode the notions of procedures, processes (remote procedure calls in general).

Operation declarations

An operation declaration gives one or more operation definitions separated by commas.

op operation_definition, operation_definition, ...

Each operation definition specifies the name of the operation, the types of its parameters and the type the return value (if any). The operation definition can optionally specify a restriction on the way the operation can be invoked.

operation_id operation_specification
operation_id operation_specification operation_restriction

The operation specification specifies the parameters and an optional return value.

( parameter_specification_list )
( parameter_specification_list ) returns ret_type var_name

The form of the parameters specification list is a (possibly empty) comma separated list of formal parameters

param_specification, param_specification, ...

Each parameter specification specifies one or more parameters of the given type.

var_type var_id, var_id, ...

Parameters may be arrays in which case their name is followed by the dimension. Thus, return identifiers and variable identifiers has two forms.

identifier           or      identifier dimension

Dimensions declarations are equal to those of variable declaration. One extra form of dimension,*, is available for parameters (not return values), indicating that the procedure is polymorph in the dimension of the array.

Operation Restriction

The operation restriction indicates how an operation can be invoked. It take one of four forms

{call}     {send}     {call,send}     {send,call}

The meaning of the different invocations is discussed below.

The following are three examples of operation declarations.

op insert(person item)
op differ(string[*] s1,s2) returns int place
op left(int i) returns int lft

Parameter and return names are optional in operation declarations since the names must be specified by the proc or in. Thus, the above example could just as well be written as

op insert(person)
op differ(string[*], string[*]) returns int
op left(int) returns int

However, because the name of the parameter documents its role, it is usually useful to include it. An operation can also be declared using operation types, which are shorthands for operation specifications. This is explained further below.

Parameter Passing

You can specify how individual parameters are to be passed to the procedure by preceding the type by either var, val, res and ref with the following interpretation:

val
Value parameters are copied in
res
Result parameters are copied out
var
Variable parameters a both copied in and out
ref
Reference parameters are passed by address
The default is value parameters. Typically, large things such as arrays are more efficiently passes as references. If you pass parameters as references or variables any modifications made on the parameter will effect the corresponding variables of the caller. Use value parameters if you want immunity to accidental modification of information passed to procedures.

Proc declarations

A proc implementing an operation has one of two forms depending on whether the operation is declared to return anything or not. A proc is associated to an operation by having the same name as that operation. When associated to an operation without return value, a proc declaration has the form:

proc operation_id ( formal_id_list ) { block }

A proc tied to an operation with return value has the form

proc operation_id ( formal_id_list ) returns result_id { block }

The formal identifier list is a list of zero of more comma separated identifiers.

id, id, ...

Returning values

Values are returned by assigning to the variable which has been defined after the returns keyword in the procedure declaration. Assigning the return value does not terminate the execution of the procedure. Termination of execution occurs either when execution reaches the end of the body of the procedure or when a return statement is found. The form of a return statement is simply

return

Execution of a return statement immediately stops the execution of the procedure and returns control to the caller, returning the last value assigned to the return variable.

Operation Invocation

Generally, there are three syntactic forms of operation invocation, two of which are statements and one is an expression.

call operation_id ( expr_list )
send operation_id ( expr_list )

The operation is the name of an operation. Usage of the send keyword results in the creation of a new process in order to run the body of the proc implementing the operation (and is thus not part of the sequential part of MPD). The expression list is a (possibly empty) list of expressions which evaluation result will be passed as parameters to the operation. The number of expressions should be equal to the number of formal parameters.

expr, expr, ...

An operation returning a value can be used in expressions. The syntactic form of of such operation calls is formed by omitting the call keyword.

operation_id ( expr_list )

Consider

int a = id(10)  # assign 10 to a

call id(10)     # call the id procedure and throw away the result

send id(10)     # invoke the id procedure asynchronously

int l = len(arr) # calculate the length of array arr

A command of the form

operation_id ( expr_list )

is simply a shorthand for

call operation_id ( expr_list )

Operation Types

An operation type declaration introduces a new name for an operation type that can be used as a synonym for the specify type of operation. Operation type can be used in operation and capability declarations. Operation type are great tools when you want to use operations of the same type more than once. An operation type declaration has the form

optype optype_id = operation_specification operation_restriction

The specification and restriction has the same forms and meaning as described in the previous section. The = can be omitted if desired. The following three examples of optype declarations are all equivalent.

  optype intfun = (int n) returns int sum
  optype intfun (int n) returns int sum
  optype intfun (int) returns int

As mentioned above operations can be declared using optypes instead of explicitly giving the operation specification. The general form of this is

op optype_id operation_id, optype_id operation_id

An example of this is

op intfun fact

It declares fact to be an operation whose specification is given by intfun. The operation fact can then be implemented by a proc.

proc fact(n) returns prod {
  prod = 1
  for [i = 2 to n] {
    prod *= i
  }
}

Operation type can be used to make the program self documenting by intentional use of types. The also make the source text more compact. Compare the following example

op expr   () returns int
op term   () returns int
op factor () returns int

to the following more compact version

optype ftype = () returns int
op ftype expr, ftype term, ftype factor

Operation Capabilities

An operation capability is a pointer to an operation. Such pointer can be assigned to variables, pas as parameters and used in invocations statements, with the effect of invoking the operation the capability points at. A variable or parameter is defined to be an operation capability by declaring its type in one of the following ways.

cap operation_id
cap optype_id
cap operation_specification
cap operation_specification operation_restriction

An operation capability can be bound to any operation having the same parameterization. When the parameterization is compared, only the signatures of formals and return values matter; formal and return identifiers are ignored. Capabilities can be compared for equality using the == and != relational operators. Order comparison using the other relational operators (e.g. <, etc) is not allowed. Given the following operations

op d(int x)
op e(var int x)
optype s = (int x)
op f(real x) returns real y
op g(real a) returns real b

we can declare the following operation capabilities

cap d x  #capability x pointing to operation of the 
         # same form as operation d
cap f y  #capability y pointing to operation of the 
         # same form as operation f
cap s z  #capability y pointing to operation of the 
         #same form as operation type f

Some ways the variables can be used

x = d              # x now points to operation d

x(387)             # invoke d through capability x 
                   #  with argument 387

if (...) { y = f } # make y point to f or g
else     { y = g }

write(y(4.351))    # invoke whatever y points to, and
                   #  print the result

z = x

if (y == f) { ... } # capabilities can be compared

However, the following statements are illegal (taking the comments into account):

x = e   # the parameter type of e is var int 
        # but x is val int

d = e   # d is an operation, you cannot assign to operations

z(3,45) # z requires a single parameter

Procedures

Generally, a procedure is a body of code that can be called from other code. It takes parameters and can return a value. Procedures in MPD are shorthand for a combination of the more general constructs discussed above, an op followed by a proc. Here we give some further examples.

A procedure which does not return a value has the form

procedure ( param_spec,param_spec, ... ) { block }

A procedure returning a values has the form

procedure ( param_spec,param_spec, ... ) 
  returns ret_type var_name { block }

Parameter specification declares one or more variables of the same type with the possibility if specifying how the parameters are passed, just as for operation parameter declarations above. are passed:

procedure a() {  # a procedure returning nothing, 
                 # taking no parameters
  ...
}

procedure id(int a) returns int b { # the identity procedure
  b = a                             #  that returns its argument
}

procedure len(int a[*]) returns int b { 
# a procedure that calculates
# the length of the given array

  int l = 0                             
  for [i in lb(a) to ub(a)] {
    l++
  }
  b = l
}

Passing by reference (ref) is more efficient for passing large data structures.

Example

resource main()
  procedure factorial(val int x)  {  # "val" tag is optional 
      int fact = 1
      for [ i = 1 to x ] { fact *= i }
      write(x, "factorial is", fact)
  }
  # find factorial for each positive number input
  int x = 10
  while (true) {
      if (x == 0) { exit
      } else if (x < 0 ) { write("number must be >= 0")
      } else if (x > 0 ) { call factorial(x)
      }
      x--
  }
end

Usage of Array Parameters

As describe above; when passing arrays, one additional form is allowed. The sizes of an array and string parameters (but not the results) may depend on the arguments of the invocation. This is denoted by the use of a * for a range bound or a string size. Here is an example:

resource main()

  procedure sort(var int a[1:*])  {
      for [ i = lb(a) to ub(a)-1,
         j = i+1 to ub(a) st a[i] > a[j] ] {
             a[i] :=: a[j]
      }
  }

 int x[1:20], y[2:30], z['a':'z']
 # initialize x, y  and z
   ...
 
 sort(x)
 sort(y)
 sort(z)

end

Note that sort has a var parameter, so the changes to the parameter by the call have an effect on the value of that parameter. A ref parameter could also be used here.

Here is an example involving strings. It takes a string and ``centers'' it:

resource main()

procedure center(var string[*] s)  {
  int fill = maxlength(s) - length(s)
  int front = fill/2
  s = string(([front] ' ')) || s || string(([fill - front] ' '))
}

# read in a string; center it; repeat.

string[20] str # maxlength(str) is 20
while (read(str) != EOF) {
   center(str); write(str)
}

end

This kind of polymorphism over array sizes can be very useful. For instance consider an array indexed from 'a' to 'd'. We can use len to calculate its length.

  int b['a':'d']
  write(len(b)) # writes 4 on the terminal.

 

Warning!

The size of the arrays are completely unchecked statically! For instance, consider the following statically correct procedure that takes a one element array as input and returns a two element array

  procedure runtime_fail(int a[1]) returns int b[2] { 
    b=a      # would result in a runtime error!
  }

  runtime_fail([2] 1) 
# would result in another runtime error!




next up previous contents
Next: Resources and Globals Up: The MPD Programming Language Previous: Sequential Control   Contents
David Sands 2003-09-05