[e-lang] Shortcuts for asynchronous control flow
Plotnikov, Constantine A
cap at isg.axmor.com
Fri Jul 7 09:46:20 EDT 2006
David,
1. *On serialized.* I drop proposal about "serialized". It can be
replaced with "using" and careful design of request queue.
2. *On all/seq.* I accept your proposal about "all {} and {}". The
problem with operator syntax would be composing results. I also accept
your proposal about "seq {} then {}". But with one addition after "seq"
and "then" there could be an optional "def <pattern> := " part that is
analog of previous "wait" statement. The samples below use reworked
structure.
# forward sample
def forward(in, out, maxReadDataSize): vow[int] {
def forward(data, in, out, sum) : any {
return seq def [_,newData] := {
all {
if(data != null) {
out<-write(data)
}
} and {
in<-read(maxReadDataSize)
}
} then {
if(newData == EOF) {
sum
} else {
forward(newData, in, out, sum + data.length())
}
}
}
return forward(null, in, out, 0)
}
# compare stream sample
def compareStreamsByByte(in1, in2) : vow[boolean] {
return seq def [b1, b2] := {
all {
in1<-readByte()
} and {
in2<-readByte()
}
} then {
if(b1 != b2) {
false
} elseif(b1 == EOF) {
true
} else {
compareStreamsByByte(in1, in2)
}
}
}
I'm also thinking about adding optional "catch( <pattern> )" and
"finally" parts. The semantics would be following (assuming that
seq/all returns promises, expansion would be different in case of
immediate return ):
# basic form
seq {...} then {...} catch(<pattern>)) { <body> } finally {<body2>}
# expanded as:
{
def seqRc := seq {...} then {... }
when(null) {
return seqRc
} catch(<pattern>) {
<body>
} finally {
<body2>
}
}
# basic form
all {...} and {... } catch(<pattern>) { <body> } finally {<body2>}
# expanded as:
{
def allRc := all {...} and {...}
when(null) {
return allRc
} catch(<pattern>) {
<body>
} finally {
<body2>
}
}
Overall, the difference is that "when" structures control flow through
promises (data flow) and "seq"/"all" structure control flow using
composition of asynchronous processes. For IO (or other activity full of
side effects), it is simpler for me to think about composition of IO
processes rather than about data flow. In IO sequence of operations
matters highly, so data flow is not a good development abstraction for
it. The "seq"/"all" allow to record intentions about execution order of
subprocesses explicitly. The intention does not have to be
reverse-engineered from data flow and E execution semantics. Note that
the "using" expression is also expressed in terms of composition of
asynchronous processes. The body process is wrapped into pair of
open/close processes.
I think that both mechanisms should present. And "when" looks like a
better primitive than "all"/"seq". I see how "all"/"seq" can be easily
built upon "when", but I'm not sure about reverse.
Another difference between all and "when" is that "all"/"seq" avoid
creation of temporary promises. Also if all branches resolve in this
turn, both all and seq could produce a value during this turn. This is
important for optimization. Another possible optimization is that
compiler might be able to compiler most of "all"/"seq" by directly
implementing resolver interface instead of using promises. This could
reduce memory pressure a bit.
3. *On file API*. Note that it is better to create stream in the same
vat where files exits. For first thing is that file system access could
be visualized and "file" you working is not necessary is mapped to a
real file system, so this particular implementation of input stream
could be simply unsuitable.
Other thing is that native asynchronous API could be used to support
working with file system like it is often done with sockets. For example
on windows it would be so called overlapped IO, on Linux it can be AIO
(Linux 2.6). Implementation based on such asynchronous API would likely
require that many streams should live in the same vat (at least support
for network Java NIO interfaces was difficult to implement until I have
done so, see
http://svn.sourceforge.net/viewcvs.cgi/asyncobjects/asyncobjects/trunk/src/net.sf.asyncobjects.net.nio/src/net/sf/asyncobjects/net/nio/
for examples [btw samples from the project do work (as for rev 31), and
the project was the place where I have prototyped serialized and using]).
4. *On exceptions from close()*. I need to think more about it for
Sebyla. The approach "last exception wins" is a simple and consistent,
but it does have the problem that you have described. However for E, the
behavior must match when/finally behavior in order to be consistent with
the rest of the language. If it is not what you like, it would be better
to fork out a thread about it because it is a general language issue.
"using" looks an utility expression over when/finally and I think that
it should work like it.
Constantine
More information about the e-lang
mailing list