[e-lang] Q: asynchronous tail recursion?
Constantine Plotnikov
constantine.plotnikov at gmail.com
Wed Dec 27 04:22:56 CST 2006
Is tail recursion supported for asynchronous loops in E? My current
guess (based on E.send*() API) that tail recursion for asynchronous
processes is not supported for loops that return a value and it cannot
be supported unless changes to API and to kernel E expansions are
made.
So the following loop is invalid as it eventually fails with
out-of-memory error due to unlimited number of promises created with
each referencing previous one:
var stopFlag = false;
...
# loops ends under following conditions
# - stop flag is set to true and new connection is accepted
# - exception when sending handle message to servlet
# - exception when accepting connection
def handleConnections(serverSocket, servlet) : vow {
return when(serverSocket<-acceptConnection()) -> done(con){
return if(stopFlag) {
con.close();
null
} else {
servlet<-handleConnection(con)
handleConnections(serverSocket, servlet)
}
}
}
Note that code here assumes that missing catch clause means the same
meaning as "catch problem { throw problem }" when "when" operator
yields promise.
The method could be rewritten not to create out-of-memory problem:
var stopFlag = false;
...
# loops ends under following conditions
# - stop flag is set to true and new connection is accepted
# - exception when sending handle message to servlet
# - exception when accepting connection
def handleConnections(serverSocket, servlet) : vow {
def [rc, resolver] := makeVow()
def innerLoop() {
when(serverSocket<-acceptConnection()) -> done(con){
return if(stopFlag) {
con.close();
resolver.resolve(null)
} else {
servlet<-handleConnection(con)
handleConnections(serverSocket, servlet)
}
} catch e {
resolver.smash(e)
}
}
innerLoop()
return rc
}
However I have a feeling that this should be a work done by compiler
considering that recursion is a primary mechanism for organizing loops
in such situation. And lack of tail recursion support looks strange
considering that there are no technical barriers for its
implementation here. Techniques used for implementing functional
languages is applicable here. For example
1. The primary variant of E.send should accept resolver as argument
rather than return ref. Ref returning variant could be left as an
utility method.
2. Translation of asynchronous methods in kernel E should be different
from translation of near methods. They should take a resolver as
argument rather than return value. There are at least two approaches
for this:
a. "to someMethod(arg):int" expands to two methods "method
someMethod(arg):int" and "method post__someMethod(resolver, arg)".
First one is used for near calls, the second for asynchronous calls.
Depending on situations one method could be implemented using other.
b. Introduce new keyword "on" so that "on someMethod(arg):int" will
expand to "to someMethod(resolver, arg)" and will perform asynchronous
tail recursion optimizations.
3. Promises should be created at call site rather than in method
implementation and they should be created only when needed. For
example:
def x := a<-test()
Will translate to the following:
def x : = {
def [tmp, resovler] = makeVow()
E.send(resolver, a, "test");
tmp
}
And
on test() : any {
doSomeghing();
return a<-test();
}
Will translate to the following:
to test(resolver) {
try {
doSomeghing();
E.send(resolver, a, "test");
} catch(e) {
resolver.smash(e)
}
}
It is also possible to introduce additional optimizations. For example
promises that are just passed to "when" operator do not have to be
created as resolver interface could be implemented directly on the
spot and passed to the invoked operation.
Constantine
More information about the e-lang
mailing list