|
An important idea behind BETA was to create such a general,
orthogonal, and minimal language that it would be easy to get to know
well, and still it would be so expressive that anything could be
written in it in an elegant and maintainable form.
Because of this, there should not be many predefined languge
entities. However, a few are needed, and they are presented here.
Basic patterns
All patterns are either constructed from other patterns (using
specialization and main parts) or predefined.
The set of predefined patterns may be viewed as a parameter, defining
a family of BETA languages which differ only in the choice of
predefined patterns. Because of this, it has not been given that much
attention what selection of basic patterns are present in gbeta, it
simply supports the same basic patterns as the Mjolner BETA System as well as a
few extras. It would be nice to have different widths of integers,
unicode characters, and infinite precision arithmetic types as well,
but that remains a future project. In Mjolner BETA System, by the way, some
changes are being introduced in this area right now.
The basic patterns for simple values are boolean ,
char , integer , real , and
string . The string basic pattern is new
compared to Mjolner BETA System, and it will be described below. The others are
well-known. Each integer object, e.g., is a container
for one value from the set of integer values supported (again a
language family parameter). In evaluations, an integer
object will deliver one integer value ("its value"), and when assigned
to it will accept one integer value. Similarly with the others. All
these basic patterns have a primitive value attribute.
It is not an object, and you cannot obtain a reference to it or ask
for its type or inherit from it. It is used for access to the value
of primitive objects in specializations, e.g.:
(#
talking_integer: @integer
(# do 'Hi, mom, I''m '->stdio;
value->putint;
' now!\n'->stdio
#)
do
5->talking_integer
#) |
These input/output properties are the atomic (irreducible) elements in
the recursive definition of input/output properties: Any
object will deliver a (possibly composite) value when evaluated, and
this value is computed by looking up exit -lists
recursively until a primitive pattern or an object reference
or a pattern reference
("[]" , "##" ), or a literal
expression (like "3.1415926" or "'Ho! Ho!
Ho!'" ), or a primitive value (like the index variable of a
for -imperative) is encountered.
A string contains a string of characters as a value,
i.e. it is immutable. This provides a nice compromise between sharing
references to (heavy) "text" objects and copying these objects all the
time. The problem is that copying a text object everytime it is used
as an argument is too expensive, and sharing it by tranferring an
object reference gives rise to aliasing. This is bad aliasing, because
it is not motivated by the modeling relation (there is no analogous
sharing in the conceptual model of the application domain), but
exclusively motivated by performance considerations. It is possible
that the problem lies in the fact that the standard text
pattern in the Mjolner BETA System basic libraries uses repetitions in the
enter - and exit -lists, and repetitions of
instances of basic patterns are a too low-level construct in the
Mjolner BETA System to support read-only access (which is normally achieved
using a pattern that has only an exit -part and which
exits whatever we want to have read-only access to).
Anyway, thestring basic pattern provides a solution
because it allows sharing at the implementation level (value
assignment of a string can be just an assignment of a
pointer) and semantically works like other values: If you have a "7"
in an integer variable, nobody will ever be able to change "7" into
anything else, and hence the variable will not change because of
"internal aliasing" (somebody else referring to the contents). As a
result, a string is both fast and safe. On the other
hand, it cannot be "edited", so if you want to change the individual
characters a lot, copy the string into an oldfashioned
repetition of char , and change the contents as you like,
then possibly copy it back into a string .
There are three primitive operations on string s:
aString.length delivers the length of the string,
aString.at accepts an integer value and
delivers the character at that position. In the future, there will
possibly be some substring support, but string generally
should remain a simple thing. Finally, string has a
value attribute like the other basic (value) patterns.
As an example:
(#
s: @string
do
'test'->s;
s.length->putint;
2->s.at->stdio
#) |
Concurrency
As mentioned in the co-routine and concurrency
sections, concurrency has been re-defined from being a basic property
(distinguishing "objects" and "components") into being a matter of
types. An object has its own stack of execution iff it is a
specialization of component , which is one more basic
pattern. Along with component goes
semaphore , since concurrency control is needed as soon as
there is concurrency.
The primitive commands available with component have been
mentioned in the concurrency section, and the primitive
commands of semaphore are
e.g. aSem.P , aSem.V , and
aSem.count . These command are standard semaphore
commands as defined by Dijkstra. The P command and the
V command are executed by the run-time system in such a
way the the number of V commands on any given
semaphore is at all times greater than or equal to the
number of P commands. This means that a thread can be
delayed when it attempts to execute a P command, and it
will not continue the execution before "somebody else" executes the
V command on the same semaphore . This
concurrency control primitive is sufficient to create higher level
concurrency control abstractions, such as monitors and ports. Lots of
details about this can be read in "the Beta book." Finally
aSem.count evaluates to the number of threads waiting to
execute because of the P/V command count constraint.
This concludes the gbeta tutorial.
| |