|
Evaluations are related to the input/output properties of various
entities, and that has to do with "extracting the value from" or
"impressing a value upon" an entity. Syntactically, we're talking
about expressions and about the following syntactic elements:
One very important property of gbeta evaluations is that they are
determined by the static analysis information. Even though a
specialization hierarchy may have a different number of arguments at
different levels of specialization, it is always the statically known
types of the entities being evaluated or assigned to that determines
the structure of the values delivered and accepted.
Evaluation lists
In general, expressions may be composed into tuples in gbeta.
If X1..Xn are expressions, then the
following is also an expression:
Moreover, if T1..Tn are assignable (the
grammar term is <Transaction> ) then
is assignable. As an example, if you could do:
then you can also do:
It is important to note, however, that this does not imply a
"parallel assignment" semantics, which computes the composite value of
the entire left hand side of the arrow, and then impresses this value
on the right hand side. In contrast, the programmer should
not rely on a particular ordering in the value transfers.
The reason why the language has such a "messy" semantics is
performance. If the semantics was the "pure" parallel assignment,
every single assignment and method call in a gbeta program would
give rise to the allocation of temporary data, and presumably only a
very small fraction (though an undecidable fraction) of the program
would actually rely on this semantics. Consequently, all programs
would have twice as bad performance, just because we wanted the
proverbial "swap" to look good:
(x,y)->(y,x) (* NB: not a swap! *) |
The message is: if you need temporary variables for swap-like actions,
declare them!
Assignments and method invocations
As mentioned before, assignment looks like this:
Syntactically, this is an assignment evaluation. LHS is
evaluated, that is: the current value is extracted from it.
RHS gets this value impressed upon it.
In the special case where RHS is an object, this has the
effect which is known as (value) assignment in many languages. It
changes the state of the object denoted by RHS .
In the case where RHS is a pattern (which is then by
coercion instantiated), the effect is known as method
invocation or procedure call in many languages. The amonymous object
which is created by coercion receives the given value and is then
executed, corresponding to the transfer of actual arguments to a
method or procedure followed by the execution of its body. Note that
this means that "arguments are moved in front of the method name in
gbeta." What looks similar to this in other languages:
obj.aMethod(arg1,arg2,..) // NB: not BETA syntax |
looks like this in gbeta:
(arg1,arg2,..)->obj.aMethod |
You might even agree that this syntax is more consistent and readable:
// in an ordinary programming language ;-)
// assume "type result = {result1:type1; result2:type2;};"
var res: result;
res = proc3(proc2(proc1(arg1),arg2))
result1 = res.result1;
proc4(res.result2); |
(* in BETA syntax *)
(arg1->proc1,arg2)->proc2->proc3->(result1,proc4)
|
Reference assignments
As mentioned above, the default assignment semantics in gbeta is
value assignment, i.e. the transfer of some representation of
the state of one object into another object, changing the state of the
other object but not changing the object identity.
Another important kind of assignment is the reference
assignment, which is the default assignment semantics in many
object-oriented languages, e.g. Smalltalk and Eiffel (for non-expanded
types). This implies a change of object identity, and hence it is
only possible in gbeta for attributes which denote object references
(i.e. attributes declared with the "^" declaration flag,
i.e. dynamic references in traditional BETA terminology).
When LHS evaluates to an object reference with an
acceptable type, an (object) reference assignment to the
object reference denoted by or can be written as:
There is an analogous case for patterns: When LHS
evaluates to a pattern reference with an acceptable type, a
pattern reference assignment to the pattern reference denoted
by pr can be written as:
After this, the value of pr will be the pattern reference
resulting from the evaluation of LHS .
In fact, this is just a special case of the coercion
described earlier, since we are really requesting that or
should be impressed a value as an object reference, and
similarly for pr . The specialty is that this is only
allowed when the coercion is trivial (e.g. or is already
an object reference). Consider coercing something into a temporary
entity (e.g. from pattern to object reference) and then assigning that
entity, and immediately discarding the temporary; the assignment would
simply "disappear!" Since this is not a desirable scenario,
non-trivial coercions are forbidden on the receiving side of a
reference assignment.
For example:
(#
p: (# #);
pr: ^p;
opr: ##object
x: @p;
give_x: (# exit x[] #);
give_typeof_x: (# exit x## #);
do
x[]->pr[];
give_x->pr[];
x##->opr##;
give_typeof_x->opr##;
pr[]->x[]; (* rejected! non-trivial coercion of 'x' *)
#) |
Enter- and exit-parts
The input/output properties of basic patterns like
boolean and integer are predefined and
simple: an integer is able to accept one value from the
set of integer values supported in the language, and it will deliver
one such value when evaluated; similarly for the other basic patterns.
Attribute denotations (e.g. names) with the explicit coercion
markers "[]" and "##" also have atomic
input/output properties, e.g. if or were an object
reference, or[] would deliver and accept one value
belonging to the set of object references with a type that matches the
declaration.
The input/output properties associated with a main part
are defined in terms of the enter - and
exit -parts. An enter -part (and an
exit -part) may contain a single evaluation or an
evaluation list (a tuple). The input (resp. output) properties are
computed by looking up the entities mentioned in the evaluation
recursively, until atomic entities have been found. In general this
process computes a nested tuple of primitive input (output) types.
An assignment is accepted by the type-checker iff the left hand side
and right hand side have identical nested tuples of value types as
output rsp. input properties. Interactively, you can inspect these
tuples using the commands assigninfo and
evalinfo .
Do-parts
The do -part of a main part participates in
input/output. The simple (and complete) rule is that the
do -part is always executed. This means that the
do -part is executed before an exit -part is
evaluated, and the do -part is executed after an
enter -part has been assigned to, and the
do -part is executed between the assignment to the
enter -part and the evaluation of the
exit -part if both processes are taking place.
Example 3
Here is an example of this recursive lookup process:
(* FILE ex3.gb *)
-- betaenv:descriptor --
(#
point:
(# x,y: @integer
enter (x,y)
do INNER
exit (x,y)
#);
rectangle: (# ul,lr: @point enter (ul,lr) exit (ul,lr) #);
i,j,k,l: @integer;
p1,p2: @point;
r1,r2: @rectangle
do
(3,4)->p1->(i,j);
p1->p2;
(p2.x+i,p2.y+j)->p2;
(p1,p2)->r1->((i,j),(k,l));
r1->r2
#) |
Note that one useful way to use enter - and
exit -parts is to define the semantics of value assignment
for a pattern. The do -part in point is only
there to annoy you when single-stepping: generally,
do -parts take place in everything, as you will see! ;-)
The next section presents the BETA concept of inheritance,
designated specialization.
| |