function foo(a=1, util.Vector b) {
a = b.size()-1;
return(b.elementAt(a));
}
or using the more generic assignment operator
x = function(a,b) {
..
}
The latter of these illustrates anonymous functions -
the right-hand-side of the assignment is an
anonymous function.
Anonymous functions are treated as simple components
of different types of expressions and can be specified as arguments
to functions, etc.
For example
vector.apply(function(x) x.mean());(assuming
vector has an approprite apply() method).
As with S and R the return value of a function is provided by an
explicit return expression or is taken as
the value obtained by evaluating the last expression of the function's
body.
Note that in declaring a function in Omega, one can optionally
specify the type of the return value. If it is omitted, not type
checking is performed and the canonical java.lang.Object is assumed. The benefits of
providing the type information include
final
or const keyword
which makes it read-only.
Any function parameter can contain type information in the form of a class or primitive specification and arrays (which are not handled yet, I believe!) For example, the function definition
function foo(util.Vector x, int y, z) {
n = x.size()
int tmp = n+10;
local bar;
}
has three paramters, 2 of which are constrained to be of a
particular type. The evaluation model will take care of throwing an
exception if the types don't match. In the case of the paratemer z,
there is no type so any Object will suffice.
The benefits of this include improved performace in compiled code
and simpler error checking. Instead of examining the class of an
argument or attempting to invoke methods within try-catch blocks,
we use the system to guarantee a type and give information to the user
about the inconsistencies if they arise.
Note that in the body, we can optionally type local variables
(tmp and bar are local).
In the case of tmp, any future assignments to it will
enforce that type.
In the case of bar, there is no type information but
we are declaring a local variable of that name in this evaluation
frame.
This distinguishes it from any other variable of the same name
(bar) in other databases in the search path.
Without this, another instance of this variable name
might be overwritten by an assignment within this scope.
It is always preferrable (for understandability and reduction of
odd bugs) to declare local variables.
Return values of functions can also be type checked.
util.Vector function foo(a,b) {
x = ...;
return(x);
}
When this function is evaluated and returns,
the class of the the return value
is checked against the declared type of the function.
If they are not compatible, an exception is thrown.
The default or implied type declaration is Object
so that any type of object may be returned.
Again, this feature aids in debugging in addition to allowing semantic analysis on code to provide automated parellism, compilation, etc.
function foo(x = 1, util.Vector y = new Vector(10), z = foo(x))Note that the default value of
z in our example
is not overly clear. Will the value of x
in the call to foo be found in this calls frame
or in the callers frame?
This is open for debate.
static
as a qualifier to variable(s) declaration(s)
that are to be considered persistent across invocation of the
function. Other than this, the same specification and rules
that apply to functions are in effect for closures.
The parser and supporting classes take care of instantiating
an object of the appropriate class. The evaluation is done using
two databases/frames - one for the arguments as in a function and a
second that stores the persistent variables.
For example,
function foo(a,b) {
static local numCalls = 1;
static Vector z = new Vector();
..
numCalls++;
..
}
Initialization of the static variables is done just once, when the
function is first invoked. Accordingly, different sessions may have
different version of the function. We might change this in the future
to evaluate the objects when the function is defined. This is possible
using the default evaluator returned from the evaluator
manager that is accessible through the global variables.
this inside a function to refer to a function of
the same name but perhaps with different arguments. In that way, we
can check for that name in a method call or a match against the name
of the function. The use of this works for
anonymous functions as well as regular named function objecs.
z = function() {
// not called
}
foo = function(x) {
z = function(a,b) {
a + b;
}
z(x,1)
}
Calling foo() now will lead
to the local version of z being invoked
rather than the global one.
The scoping rules are still up for debate. Either the S or R versions are easy to implement. At present, the basic scoping rules apply so the search path is searched from top down. This includes all the frames attached by earlier function, List, loop-bodiers, etc. evaluations. Compilation issues will help to determine the scoping rules.
Math.randomappears like a field acccessor, but resolves to a method acccess. Rather than returning this as a simple Java Method object, we wrap it as an evaluable object of class StaticMethodAlias or MethodAlias This can then be used in the following manner
f = Math.random f()and for non-static methods
f = x.plot f(v1, v2)
In addition to using these method objects as convenient aliases, the addition also allows one to extract methods and examine it to see what arguments it takes, what it returns, etc. In other words, it allows us to easily examine the native Java classes.
If the accessor resolves to numerous overloaded methods, then we
return a Vector of the StaticMethodAlias or
MethodAlias objects.