blob: 7d9f4e51c48245d6a89ebc8191560e3b584cee05 [file] [log] [blame]
A block has a list of typed parameters and a return type, and it
encapsulates a sequence of statements.
Calling a block:
Block<Boolean, (Integer i, String s, Binary b)> b =
{ (Integer i, String s, Binary b) | return s.length > i; }
b.getSignature().getInput() --> (Integer i, String s, Binary b)
b(1, "Humba", 0x78BDC8); // operation call with three args of
// type { Integer, String, Binary }
b(i=1, s="Humba", b=0x78BDC8); // assigning by name rather than pos
p=(i=1, s="Humba", b=0x78BDC8);
b p;
Block / operation argument's exposed association ends are
syntactically exposed like variables of the block. This would mean
that the identity of the compound object passed as argument is
revealed and cannot be easily accessed. Of course, if one of the
associations to the components of the compound argument type is
modeled as bidirectional, that would again be possible:
{ (param <-> Integer i, String s, Binary b) | return i.param }
would be a block that returns the compound argument value.
Compare with operation call convention. The operation declares a
signature consisting of a set of named input arguments and a single
unnamed output argument (optionally a set of exceptions /
faults). Equivalently, an operation could be said to take a single
unnamed argument. If multiple named arguments shall be mapped this
way, the argument type needs to be a class with associations to those
argument types and association ends named after the argument names.
When an operation (or a block, respectively) tells its input argument
type, when no other type is specified, the parameter list could
instantiate that argument type by default instead of creating a new
type implicitly that then only conforms to the argument type.
Syntactically, what if "()" was the operator used to create an
instance of a (potentially anonymous) class, filling its association
slots? An operation or block invocation would be denoted by writing
the argument value after the operation / block without
parentheses. The parentheses are then only used to construct the
"tuple" value holding the argument values. If a value of the correct
type can be otherwise obtained, e.g., from a variable that holds a
value of a conforming type, the invocation would not show parentheses
at all. Example:
Class c = (-> 1..1 Integer i);
void m c;
void m(Integer i) { ... }
p = (i=3); // constructs an instance of an anonymous class that has an
// association to the (type-inferred) class Integer with
// association end name "i"
m p; // calls operation m passing the argument p=(i=3);
m(i=3); // equivalent call, only that here the (i=3) value may not
// need to create a new anonymous type but use the argument
// type of m.
void m(Integer compareTo(Object o), ...); // specifying required operations
void m(x) {
if (x.compareTo(irgendwas) < 0) {
...
} else if x.compareTo(irgendwas) > 0) {
...
} else {
...
}
}
Analogously, the parameter list specification of a block / operation
would simply be an expression that evaluates to a class. Given the
right context, something like
(Integer i)
may be read as a definition of an anonymous class that has an
anonymous association to type Integer with association end name
"i". In this short notation, the assumed multiplicity is 1..1. Other
multiplicities may be specified as well:
(0..1 Integer i) or (0..* Integer i) or (1..* Integer i)
Composition semantics may as well be expressed, as in
(0..* <>-> Integer i)
Associations that are not navigable from the argument type make little
sense. However, they are not disallowed because it may allow using
existing classes as argument types.
With leaving the argument anonymous for the implementing block, using
non-compound classes as argument type seems useless because there
would be no way of identifying the argument in the implementation.
ClassLiteral ::= '(' [ TODO...
Discussion: Blocks referencing variables of their enclosing blocks
------------------------------------------------------------------
Referencing a variable of a block's enclosing block can get confusing
if the object assigned to the variable may change between the point in
time that the block is defined and the point in time when the block
last accesses that variable. In the worst case, there may be
concurrent execution of the block and other code that manipulates the
variable, such that the variable changes its value with the block only
reading from it.
Java tries to address this problem by requiring variables that can be
used by anonymous inner class instances to be final. Of course, this
doesn't preclude the object bound to the variable to change its
state.
On the other hand, if blocks are to be used, e.g., for internal
iterators, it would be very necessary to have access to the enclosing
block's variables without having to make them all final. Especially,
if such iterators execute in sequential order, this should not create
too many surprises.
Problems occur in case of concurrency. Imagine an internal
"forallInParallel" iterator. In this case there is no execution order
guarantee for the blocks. Variables of the enclosing block constitute
"shared memory" for those concurrent threads of execution that will
require some sort of locking / synchronization.
Should concurrency be restricted to cases where the compiler can
guarantee that there is no conflicting access to any objects or
variables?
Block values vs. BlockLiteral
-----------------------------
A block is defined as a literal which occurs in a lexical context
within another block literal inside a statement which is part of the
enclosing block. Some block literals do not have an enclosing block,
for example a block defining the implementation of an operation in a
class which is not defined within the context of any block.
A block is also a value / object that can, e.g., be bound to a
variable or passed as an argument to an operation or be associated
with other objects. As such, the block value may be passed around the
system and be used as an expression (variable expression, TODO