[E-Lang] pending revision of E in a Walnut

Mark S. Miller markm@caplet.com
Mon, 27 Aug 2001 12:48:33 -0700


At 03:57 PM Sunday 8/26/01, Mark Seaborn wrote:
>A problem I have with exception-handling constructs is that they tend
>to be too liberal in where they catch exceptions from.

Curiously, your other proposal in this same email admits two expansions. One 
consistent with the new "too liberal" mixed control-flow dataflow nature of 
when-catch, and the one you present, consistent with the earlier pure 
dataflow form.  In principle, both allow problems in the when body to be 
caught and reported, which was the requirement motivating the new too 
liberal form. Unfortunately, there's still a tradeoff, as shown below.


>On a related topic, a common idiom in E seems to be to create
>(promise, resolver) pairs in order to return the promise immediately
>and resolve it in the `done' or `catch' clause of a `when' expression.
>But since `when' currently doesn't return any interesting value
>(correct me if I'm wrong on this), 

You are correct.  A when-catch expression currently always evaluates to 'null'.


>why not make `when' return a
>promise for the value that is returned by the `done' or `catch' clause
>(with the promise becoming broken if the clause executed throws an
>exception)?  

I like this suggestion a lot.  Very clever.  Accepted in principle.  On to 
the details.


>In other words, make
>
>  when(X) -> done(x) { Y }
>  catch e { Z }

Btw, that needs to be written

  when(X) -> done(x) { Y
  } catch e { Z }

or, more conventionally

  when(X) -> done(x) {
      Y
  } catch e {
      Z
  }

such is the price of the newline-significance that enables E to also be a 
shell language.

Since the "done(x) {" is supposed to look like a function header, and since 
the expansion does in fact define a function of one argument, I propose 
that, to get the effect you seek, you would need to write 
"done (x) :resultGuard {", which I will assume below.


>give the behaviour currently given by
>
>  def [promise, resolver] := PromiseMaker()
>  when(X) -> done(x) {
>    try { resolver resolve(Y)
>    } catch e { resolver smash(e) }
>  } catch e {
>    try { resolver resolve(Z)
>    } catch e2 { resolver smash(e2) }
>  }
>  promise

[Curlies above corrected according to previous explanation. ":resultGuard" 
ignored for now.]

This would be upwards compatible from the old pure dataflow-oriented 
when-catch, while in principle fixing its fatal flaw: a problem thrown by Y 
would still, in principle, be reported to someone -- it would break the 
promise that the when-catch evaluated to.  Contrast with the following 
expansion, which would be upwards compatible from the new "too liberal" 
mixed control-flow/dataflow oriented when-catch:

  def [promise, resolver] := PromiseMaker()
  Ref whenResolved(X, def done(temp) {
      try {
          def result :resultGuard := try {
              if (Ref isBroken(temp)) {
                  throw(Ref optProblem(temp))
              }
              def x := temp
              Y
          } catch e {
              Z
          }
          resolver resolve(result)
      } catch e2 {
          resolver smash(e2)
      }
  })
  promise

Note that the inner try-catch corresponds to the current expansion.

In both expansions, if Y or Z is evaluated and it successfully evaluates to 
a value (that's successfully coerced by :resultGuard), then the promise will 
resolve to that value.  Also, in both, if Z is evaluated and it throws a 
problem, the promise will be smashed with that problem.

The difference between the two is in what happens if Y is evaluated and 
throws an exception.  In the first expansion, the problem is reported only 
by smashing the promise.  In the second, the problem is reported only to the 
catch of the when-catch.  While I see that the first is purer, an E 
programmer would typically only receive such an error report if they did 
another when-catch on the promise for the result of the first when-catch.

An interesting tradeoff.  My current inclination is to go with the semantics 
shown by the second expansion, but do it by modifying the 
"Ref whenResolved(..)" method to create, return, and resolve the promise, 
rather than making the expansion do this.  The expansion would be modified 
only to allow a ":resultGuard", which would become the guard on the "done" 
function, just as it looks like it is.

  when(X) -> done(x) :R {
      Y
  } catch e {
      Z
  }

would expand to:

  Ref whenResolved(X, def done(temp) :R {
      try {
          if (Ref isBroken(temp)) {
              throw(Ref optProblem(temp))
          }
          def x := temp
          Y
      } catch e {
          Z
      }
  })

>[...] Also the expansion of `when' could probably remove
>the use of `try' by using eventual sends for turning control-flow
>exceptions into data-flow exceptions.)

Quite correct.  The logic for invoking the done function and resolving the 
promise, now moved into the internals of "Ref whenResolved(..)", could 
either be of the form

    try {
        resolver resolve(doneFunc(x))
    } catch e2 {
        resolver smash(e2)
    }

or

    resolver resolve(doneFunc <- (x))

The latter would postpone the doneFunc invocation for another turn, but this 
is allowed by the semantics.

I'm inclined to make the same change to "Ref whenBroken(..)".


>Using PromiseMaker explicitly creates the risk that a resolver will
>never get called, which could cause datalock.  Extending the behaviour
>of `when' gives a way of eliminating the use of PromiseMaker, reducing
>that risk and making code shorter and clearer.

This add another case -- Lost Signal -- to our list of lost liveness bugs.  
See the new updated 
http://www.erights.org/elib/concurrency/event-loop.html#deadness .
Thanks!

>This extension provides a path for exceptions from the `done' and
>`catch' clauses to flow along.  If no result flows out from the `when'
>expression, odds are that those exceptions don't make any difference
>and don't need to be reported.

My concern is losing the problem report when a when-catch is used without 
using the promise it evaluates to, which I believe will still be typical use.


        Cheers,
        --MarkM