[1388] in java-interest
Re: The need for "perform:" in Java
daemon@ATHENA.MIT.EDU (Doug Lea)
Thu Aug 31 15:57:54 1995
Date: Thu, 31 Aug 1995 12:17:51 -0400
From: Doug Lea <dl@altair.cs.oswego.edu>
To: clp@home.HarvardSq.com
CC: java-interest@java.sun.com
In-reply-to: <9508302311.AA01992@home.HarvardSq.com> (clp@home.HarvardSq.com)
[Sorry for the length!]
I think the most compelling case for closures/perform in Java has to
do with remote message passing. I'm pressing on this application not
only because I'm interested in distributed object systems, but also
because I think it's a useful way to look at the whole issue. (And
one that leads to a concrete suggestion about how to do it down
towards the end of this message.)
Several people seem to be building systems that involve one Java
process asking another one to arrange that some existing object
perform some method. The best way to do this is still an open issue.
The way you'd typically build a distributed message facility in, say
C/C++ is to to have the sender build and issue some kind of packet
that the receiver needs to decode and manually dispatch via code like:
if (command.equals("foo")) x.foo();
else if (command.equals("bar)) x.bar();
etc.
Where the `x' here is typically an object reference that the receiver
has looked up, given some kind of `externalized' name, for example a
URL-like string. (Closures don't directly help with this part,
although they make it easier to implement -- see below).
This scheme is code-intensive, so much so that people usually end up
building tools to generate such code that should, if correct,
duplicate exactly what the Java compiler does when compiling a method.
Writing such code to handle method overloading, scoping, access
protection, etc., is a challenge. Sometimes, people don't even try to
get this right.
In other words, people end up writing a set of mini-interpretors that
hit exactly the same issues that make `perform' itself controversial.
Or to diagnose the problem differently, whenever you see lots of
conditionals or switch statements in an OO program, you know you are
doing something suspicious.
Languages like Obliq (also Phantom) provide a pretty nice alternative.
In Obliq, a message is sent as a closure OBJECT that has been compiled
in the context of the sender. Issues of scoping, access protection,
and method overload resolution are handled before the recipient ever
sees it. (Besides, doing this on the sender side seems to be the only
secure, defensible policy in Java.) So the recipient can just invoke
the operation directly by calling the equivalent of `perform' on the
received closure object. (Although in the Java analog, it would first
have to verify the incoming bytecodes).
This messages-as-closures scheme can't be applied literally in Java
since the only support for sending compiled entities is at the CLASS
level (i.e., via the .class format), not the object level. However,
the required formats are quite similar. For example, the `constant
table' in a .class is basically the same as a binding list in an Obliq
closure.
While you can't send bytecoded objects in Java, for these kinds of
`one-of' messages, you could just use a whole class for purposes of
instantiating the single closure object that you want to send. It's a
bit strange-sounding, but fits perfectly into the existing Java
classloader framework (as used by Applets). In fact, no changes
whatsoever would be needed to accommodate closures on the receiver
side, since they are just ordinary classes.
This is bit heavier than one might like, but still simpler, more
efficient, easier to verify, less code-intensive, more standarizable
across different remote object systems, and potentially more secure
than building manual dispatchers.
In other words, I think that it is just The Right Way to build
native distributed object systems in Java.
But the fundamental problem here is that it is unreasonable to have
each sender define an explicit class for each kind of message that it
might ever send. In fact, usually, there's an unbounded set of
messages you might send, so it is not possible to compile them all
ahead of time.
(footnote: Even if you relax this scheme so that the `arguments' to the
method that the closure executes are sent separately (and bound
during instance creation) rather than being compiled into the
constant table, you'd still have one closure class per callable
method, which is still too many classes to force programmers to
explicitly define.)
The alternative is run-time compilation of closure classes, where only
the closures corresponding to messages that actually get sent need to
be compiled. While it has a different look and feel, this is
equivalent to direct support of at least a simple form of smalltalk
style blocks or lisp lambdas.
For example, making up some syntax and ignoring REMOTE message passing
issues for a moment, one might write:
class AClient {
void aMethod(X target, AServer server) {
...
// tell server to execute `foo(17)' on target.
Class msg = new class {
X x = target;
int arg = 17;
x.foo(arg);
};
server.perform(msg);
}
}
class AServer {
...
public void perform(Class c) { c.newInstance(); }
}
Unless you are content to feed the whole definition as a string into
the Java parser at run-time (see my and others previous posting on
this), this needs to be made into a syntactic form. The form
illustrated above is just an attempt to be concrete. The idea is that
the code in braces is taken to be the definition of the no-argument
constructor invoked by Class.newInstance for the defined closure class
(which is just an arbitrarily named direct subclass of Object). Thus
instance construction is taken as the implementation of `perform'.
One nice thing about this simplified style is that nearly all the work
in compiling the closure can be done at initial compile time of the
code containing it. The only thing that need to be built dynamically
(at closure construction time) is the constant table. Thus, you only
need a tiny fraction of the full Java compiler machinery around at
run-time. Scope rules should work in the expected way if the values
of imported symbols are pulled into the constant table at closure
construction time.
One down side of this is that it provides only the minimal usable kind
of closure. For example, it cannot return values. (You can work around
this by defining it to do a callback to the sender if it needs to
relay back results.) But this minimal form does in fact handle a lot
of the usages of Perform in smalltalk. It is a heavy enough version
that people would still have to think twice before using it. On the
other hand having such a mechanism in place allows compiler writers to
find ways to safely optimize it.
And with such a mechanism in place, it would be much easier to write
code that handled remote message passing, by putting name translation
code and the like into the closures, and passing them around in .class
format.
In any case, this the best strategy I've thought of for integrating
closures into Java. Comments? Alternatives?
--
Doug Lea | dl@cs.oswego.edu | dl@cat.syr.edu | 315-341-2688 | FAX 315-341-5424
Computer Science Dept, SUNY Oswego, Oswego, NY 13126 | http://g.oswego.edu/dl/
Also: Software Eng. Lab, NY CASE Center, Syracuse Univ, Syracuse NY 13244
-
Note to Sun employees: this is an EXTERNAL mailing list!
Info: send 'help' to java-interest-request@java.sun.com