Advanced Issues: Dynamic Control Structures


 

Control structures are things like if, for, and while. They have been part of any imperative language since the sixties as primitive constructs, i.e. something which is visible in the grammar of the language and which would get a separate description in a formal semantics for the language.

User defined control structures

In BETA, the pattern concept is so versatile that control structures can be written in the language, in stead of being built-in language constructs. The basic built-in control primitives are leave and restart. As an example of a user-defined control structure in BETA, a while loop looks like this:

loop: 
  (# while:< boolean
  do (if while then INNER; restart loop if)
  #)

and it can be used like this:

loop
(# while::(# do (n<>10)->value #)
do n+1->n
#)

Actually, I considered adding a built-in while loop to gbeta, because this is more verbose than a built-in while would have to be. Anyway, the verbosity is no problem when the control structure is more complex and special-purpose, and by the way would never be a built-in language construct.

Let us define a control structure as a language construct (built-in or user defined) which can be parameterized with one or more pieces of code (a "body", or several bodies) and which executes this body zero or more times, each time setting up a certain environment. The standard while does not provide any special environment, but the for provides an index variable with values 1,2,.. or similar.

A good example of a more complex control structure is an iteration control structure associated with a data structure which contains a number of similar entities, e.g. a list. A very common piece of BETA code iterates a list using such a (user defined) control structure:

aList.scan(# do current.print #)

It is a BETA convention to name the currently visited element current in such iteration control structures.

Dynamic control structures

Now imagine that you could parameterize your algorithms with control structures, selecting different control structures for different purposes. This might sound much like taking a routine argument in a method, thus being able to invoke some algorithm which is only known at runtime. In C++, e.g., a routine argument is a function pointer or a pointer to a member function.

In fact, this is a much stronger concept, since the control structure provides a binding environment, i.e. it gives some code at the point of application, the body, access to a set of declared names. To support this, another function pointer could be brought into play, in plain C:


#include <stdio.h>

typedef void (*callback)(char *);
typedef void (*control_structure)(callback);

void body(char *arg)
{
  printf("%s\n",arg);
}

void var_control_struct(control_structure cs)
{
  (*cs)(&body);
}

void example_cs1(callback cb)
{
  (*cb)("Once"); 
  (*cb)("upon"); 
  (*cb)("a time");
}

void example_cs2(callback cb)
{
  int i;
  char *arg="**********";
  for (i=0; i<10; ++i) (*cb)(arg+i);
}

int main(int argc, char *argv[])
{
  var_control_struct(&example_cs1);
  var_control_struct(&example_cs2);
  return 0;
}

Here's the gbeta solution:


(# control_structure: string;

   var_control_struct:
     (# cs: ##control_structure
     enter cs##
     do cs(# do value+'\n'->stdio #)
     #);
   example_cs1: control_structure
     (# 
     do 'Once'->value; INNER; 
        'upon'->value; INNER;
        'a time'->value; INNER
     #);
   example_cs2: control_structure
     (# rep: [0] @char;
     do '**********'->rep;
        (for i:10 repeat rep[i:rep.range]->value; INNER for)
     #)
do
   example_cs1##->var_control_struct;
   example_cs2##->var_control_struct
#)

The interesting aspect of this program is the fact that the do-part of var_control_struct uses a pattern variable as a superpattern. This means that the pattern being inherited from is not known before run-time, although it is declared (and hence known) at compile time that this pattern is some specialization of control_structure (possibly control_structure itself).

Inheriting from a pattern which isn't completely known before run-time is what dynamic control structures is all about.

Now consider what happens if we have some good reason to change the control_structure pattern to:

control_structure: 
  (# value: string; 
     x,y,z: @integer; 
     doit: (# .. (* some useful method *)#)
  #);

What happens is that the example_cs? patterns and the specialization of cs in the do-part of var_control_struct gets a richer name space which could be exploited, if needed. The rest of the code could also stay unchanged. One way to put it is that new facilities could be introduced in the context of a complex program, and only those places where the new facilities are actually used will have to change.

However, in the C version of the program, the name space must be specified explicitly in the argument list of body, in the typedef for callback, and everwhere a callback function is invoked. As you can see, the whole machinery and all applications of it must be adapted, not just the construct which was changed for some good reason.

Do you think that going from C to e.g. C++ would make it possible to handle this example more elegantly?

Example 7

Here's a larger example of the same technique:


(* FILE aex7.gb*)
-- betaenv:descriptor --
(# 
   loop:
     (# while:< boolean
     do (if while then INNER; restart loop if)
     #);
   
   file: (* an artificial file with two lines of text! *)
     (# name: @string;
        count: @integer;
        eof: (# exit count=2 #);
        openRead: (# do 0->count #);
        getline: 
          (# s: @string 
          do (if count+1->count // 1 then 'one'->s // 2 then 'two'->s if)
          exit s
          #)
     #);
   list: (* an artificial singleton list *)
     (# element:< object;
        init: object;
        theElement: ^element;
        scan: (# current: ^element do theElement[]->current[]; INNER #)
     #);
   
   myfile: @file;
   mylist: @list(# element::string #);
   
   (* define the iterator interface *)
   iterator: (# theLine: @string do INNER #);
   
   (* define concrete iterators *)
   fileIterator: iterator
     (# do loop
        (# while::(# do not myFile.eof->value #) 
        do myFile.getLine->theLine;
           INNER fileIterator
        #)
     #);
   listIterator: iterator
     (# do mylist.scan
        (# do current->this(listIterator).theLine;
           INNER listIterator
        #)
     #);
   inputIterator: iterator
     (# do loop
        (# while::(# do (theLine<>'quit')->value #)
        do stdio->theLine; INNER inputIterator
        #)
     #);
   
   LinePrinter:
     (# anIterator: ##iterator
     enter anIterator##
     do anIterator(# do theLine+'\n'->stdio #)
     #);
do
   'somename'->myFile.name;
   myFile.openRead;
   myList.init; 'a string'->&myList.theElement;
   fileIterator##->linePrinter;
   listIterator##->linePrinter;
   'Type strings and [ENTER].  Type "quit" to quit\n'->stdio;
   inputIterator##->linePrinter
#)

The next section is about another unusual kind of "base class," namely a virtual pattern used as a super-pattern.

 


Signed by: eernst@cs.auc.dk. Last Modified: 3-Jul-01