[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