[e-lang] Shortcuts for asynchronous control flow (long) [corrected]
Constantine Plotnikov
cap at isg.axmor.com
Wed Jul 5 15:39:28 EDT 2006
Below are some comments to issues posted by you. Grammar/spelling/bugs
comments are mostly accepted.
*Issues with par/seq*
1. The "par" and "seq" are good names. And there is no reason to drop
good names because someone else used them differently. As for
differences, I guess it would be a bad idea to drop "def" because python
uses it differently ;).
The "seq" has mostly the same semantics. The "par" statement in Occam
has different semantics but sufficiently close one. Note that while
parallel activities are initiated in the same vat. They might happen in
different vats. I expect that code in par/seq will mostly waits while
someone else will do real job (os, hardware, other parties). If real job
is done in par's branches, the par would be a special case of seq.
However othe name could be chosen instead of "par". For example, "all"
looks like a good alternative. It still short and possibly captures the
idea even better. I will think about it.
2. The sample is a bit more tricky than it looks. It has the following
properties that are should be kept if you want to re-express it with
when/catch.
- Read and write operations are done in parallel.
- While write is active no other write is performed.
I do not see how you would write it without multiple when/catch. My
variant is:
def forward(in, out, maxReadDataSize): vow[int] {
def forward(data, in, out, sum) : any {
def readPromise := in<-read(maxReadDataSize)
# this part is a bit of cheating because we do not need result of write
# beyond that it has not failed otherwise code will be more complex
def readAndWritePromise := if(data != null) {
when(out<-write())->done(_) {
return readPromise
} catch(e) {
throw e
}
} else {
readPromise
}
return when(readAndWritePromise) ->done(newData) {
return if(newData == EOF) {
sum
} else {
forward(newData, in, out, sum + data.length())
}
} catch(e) {
throw e
}
}
return forward(null, in, out, 0)
}
We could also consider other example that compares streams by byte. It
assumed that readByte returns either byte or EOF and EOF is different
from any byte value. It is also assumed that EOF == EOF.
# seq/par version
def compareStreamsByByte(in1, in2) : vow[boolean] {
return seq {
wait [b1, b2] := par {
in1<-readByte()
in2<-readByte()
}
if(b1 != b2) {
false
} else {
compareStreamsByByte(in1, in2)
}
}
}
# when/catch version
def compareStreamsByByte(in1, in2) : vow[boolean] {
def b1Vow := in1<-readByte()
def b2Vow := in2<-readByte()
def pair := when(b1Vow) ->done(b1) {
return when(b2Vow)->done(b2) {
if(b1 != b2) {
false
} else {
compareStreamsByByte(in1, in2)
}
} catch(e) {
throw e
}
} catch(e) {
throw e
}
}
To me seq/par is more readable. But I guess the results could differ.
3. Problem with statement boundaries could be solved by some prefix to
expression in order to reduce expectation (for example "<-"). Like in
sample below:
def compareStreamsByByte(in1, in2) : vow[boolean] {
return seq {
wait [b1, b2] := par {
<- in1<-readByte()
<- in2<-readByte()
}
<- if(b1 != b2) {
false
} else {
compareStreamsByByte(in1, in2)
}
}
}
Other alternative would be to explicitly surround expressions with {}.
def compareStreamsByByte(in1, in2) : vow[boolean] {
return seq {
wait [b1, b2] := par { {in1<-readByte()} ; {in2<-readByte()} }
{
if(b1 != b2) {
false
} else {
compareStreamsByByte(in1, in2)
}
}
}
}
However I would like it to stay the way it was originally proposed.
Blocks and prefixes will introduce visual clutter and will reduce
usefulness of expressions.
4. This semantics allows to report outcome of "par" completly in case of
failure. I do not see other option to report it completly. Only few
needs that complex failure semantics. Most will likely need to just
rethrow exception or to log it. For this purpose, complex structure of
the exception is no problem, particularly if logging layer will be aware
of this structure.
*Using issues*
1.
>> If body block fails, close operation is executed anyway and expression
>> fails with exception from body.
>
>
> It fails with the body exception even if the close also fails, I assume?
In Java/C# it fails with exception from close in this case. In Sebyla, I
plan to reproduce this behavior. The semantics in E must match
when/finally semantics in E and I do not remember it now.
2. My compaint about "def" is even bigger. I do not like that "def" is
an expression. I think that the name declaraions shold be statements
rather than parts of expressions. Using it in expression makes it
difficult to understand true scope of the definition.
3. "using" with multiple resource is a good idea.
4. I suggest other version of sample:
def copyFile(fileIn :File, fileOut :File) :vow[int] {
return using (def in := fileIn<-openInputStream(),
out := fileOut<-openOutputStream()) -> {
return forward(in, out)
}
}
}
Note that it does not dictate stream implementation. And stream can be
created in other vats.
*Serialized issues*
Note that main point of the construct is to allow optimization of
execution it in the same turn when there are no pending requests in
progress. If this feature is not used, then there is no point in it,
except of better displaying intention in the source.
Constantine
More information about the e-lang
mailing list