Tutorial: Evaluations


 

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:

..->..
enter ..
exit ..

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:

(X1, .. ,Xn)

Moreover, if T1..Tn are assignable (the grammar term is <Transaction>) then

(T1, .. ,Tn)

is assignable. As an example, if you could do:

a->x; b->y; c->z

then you can also do:

(a,(b,c))->(x,(y,z))

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:

LHS -> RHS

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:

LHS -> or[]

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:

LHS -> pr##

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.

 


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