|
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.
| |