Utility "Evaluator" Functions

Calls of the form
  foo(x,y)
(i.e. unqualified function-like invocations) are evaluated by looking along the searchpath for a function named foo. If none is found, we then look for a utility method named foo. A utility method is one that is available from the evaluator for doing tasks that are commonly performed, but have no natural context/class in which they might reside. For example, the method exec in exec(String) is available. These are naturally stand-alone functions but for efficiency reasons (and simple practical ones at this early stage in the development) it is convenient to put them in a class and have them compiled. Rather than confusing the role of the evaluator by adding these methods to it, we support an additional source of such functions: the function table collection.

Each evaluator allows one to add (and remove) an arbitrary object or class to a list whose elements are search sequentially to resolve these function-like calls. The same method dispatching is done using each element of the list as a potential `this' or source of a static method in the case where the element is a class. If no method is found in this list, then we look in the evaluator.

This mechanism allows one to add and remove functions dynamically without instantiating a new evaluator. It allows quasi-functions to be reordered to allow dynamic resolution of "symbols". While static methods from class objects are most natural, using instances of classes also allow one to preserve state across calls but in a non-global manner, i.e. local to the evaluator and specifically the instance of the class.

Effectively, one can think of the elements as being miniature modules or libraries that one can dynamically attach and detach.

Many of the utilities built into the different evaluators can and should now be moved into their own classes.

A simple example of how this works is shown below. We have three classes - Foo, Bar and one which contains only static methods FooBar

// add an instance of Foo as first element of list
[] this.addFunctionTable(new Foo()) 
1

// add an instance of Bar as second element of list
[] this.addFunctionTable(new Bar())
2

// call foo1, found in instance of Foo
[] foo1(1)
[1]   // a vector containing the argument squared.

//  call foo2(), found in instance of Foo also.
[] foo2()
A simple string from org.omegahat.Environment.lib.Language.Foo

// call method in instance of Bar.
[] bar()
-1

// remove the first element of the list,
// making instance of Bar the default source
[] removeFunctionTable(functionTableList()[[0]])
true

// check what the list now contains
[] functionTableList()
[org.omegahat.Environment.lib.Language.Bar@7d31aa]

//  call foo2, now resolved in instance of Bar
[] foo2()
Overridden method foo2 org.omegahat.Environment.lib.Language.Bar

// add a class as element of the list, making its static methods
// available
[] this.addFunctionTable(StaticFoo)
2

// call foobar, found as static method in StaticFoo 
[] foobar()
Static class method class org.omegahat.Environment.lib.Language.StaticFoo
[] 
This shows how this defines a secondary search path which is interrogated sequentially to look for methods. It can be used to simplify access to certain static methods. For example, we can gain access to the mathematical functions by attaching the Math class.
[6] addFunctionTable(Math)

[7] PI
3.141592653589793
[8] log(PI)
1.1447298858494002
We can alias one class name with one of our choosing.
[6] addFunctionTable(Math,"A")

[7] A.PI
3.141592653589793
One has to be careful that there is no class named A in the classpath.

Because of the non-strict matching used to identify methods which allows matching of scalars to arrays, different types of numbers (such as double/Double to int, etc.), it is possible that a less approriate method will be matched earlier in the search path. For example, if in two classes A and B, we have methods

  foo(int[] x) {
   ..
  }
and
 foo(int x){
   ..
 }
respectively. Then, if the list contains an instance of A and then B in that order, a call foo(1) will incorrectly match the first one, from A.

Of course, it is possible to fix this and it can be done as the objects are added to the list rather than each time. An implementation similar to the omegahat Method dispatching would work conveniently which would involve merging the methods as they are added by compiling a new discardable class.

Alternatively, we can identify the elements in the function table by name to provide more resolution in identifying them and their methods. When adding an Each entry to the list of function tables, one can optionally specify a name by which it can be referred to at a future time. If this is omitted, the value is taken to be the class name, with the qualifying package name removed. For example,

  addFunctionTable(Math)
yields an entry with the name Math Adding an instance of a class will, by default, use its class name also.

So in this case, we would be able to invoke the method B.foo via the command

B.foo(1)
Or more generally,
[1] addFunctionTable(new A(), "a")
1
[2] addFunctionTable(new B(), "b")
2
[3] b.foo(1)
In B.foo(int)
null
[4] a.foo(1)
In A.foo(int[])
null

Lazy arguments

Functions that require their arguments to be unevaluated such as time() Quote can now be handled in this manner also. A simple change to MethodCall allows us to search for a method in the internal function tables by the given name with a signature
 foo(org.omegahat.Environment.Parser.Parse.List,
      org.omegahat.Environment.Interpreter.Evaluator)
in a class that implements the interface LazyFunctionTable It is the responsibility of the method to evaluate the arguments appropriately. There are methods in the different expression objects to assist this.

Variable Number of Arguments

In a manner similar to the lazy arguments above, functions that take a variable number of arguments can be implemented using internal utility objects and classes. Elements added to the internal function list that implement the interface VarArgsFunctionTable are handled in this special way. Methods in these elments invoked from the omegahat evaluator have their arguments evaluated (unless they also implement the <>LazyFunctionTable<> interface) and passed a single List. The methods can the determine the appropriate
Duncan Temple Lang <duncan@research.bell-labs.com>
Last modified: Fri Sep 17 04:55:07 EDT 1999