[e-lang] Three problems with exceptions

Mark S. Miller e-lang@mail.eros-os.org
Thu, 25 Dec 2003 23:39:27 -0800


At 07:50 PM 12/21/2003  Sunday, Kevin Reid wrote:
>While we're discussing subtle bit leaks, consider the Miranda
>__printOn/1. It leaks information about what kind of object this is to
>the caller.

Yup. So does the Miranda __getAllegedType/0. I'm not really worried about 
either one (and neither were Wagner and Tribble when we went over this part 
of the system). But we should note this issue somewhere.


>> OTOH, we all know that we need terminating exceptions to indicate
>> "something bad happened", and to exit a flow of control without needing
>> explicit code in intermediate callers that don't care. This is the great
>> economy created by terminating exceptions, and its enormous benefit to
>> readability is also an enormous benefit to security that we cannot lose.
>> 
>> The new proposal distinguishes between several cases.
>> 
>> 1) Extended "escape" construct. A handler can use the new extended form of
>> the "escape" construct (to be explained) to create a capability that can
>> be passed to an object that would signal this condition. This is close to
>> Smalltalk's "failBlock style" for handling exceptional conditions. Because
>> the capability connectivity will make the potential communications visible
>> in review, a signaller can communicate what it likes to the handler. The
>> handler gets only what the signaller sent -- no stack trace info or other
>> diagnostics are available.
>
>What's the extension? Other than the absence of other means of
>communication, this sounds exactly like the current 'escape'.

Here's an example of the current escape expression:

    ? escape foo {
    >     print("x")
    >     foo(33)
    >     print("y")
    > }
    # stdout: x

    # value: 33

If foo is called during the body, then the body terminates and the escape 
expression as a whole evaluates to the argument provided to foo.

(To get things printed to stdout or stderr to appear as above, run rune with 
the --explicitIO option. Updoc does this by default, so that it can include 
such output in its regression testing.)

A corresponding example (in 0.8.24c) of the new proposed extended escape 
expression:

    ? pragma.enable("escape-handler")

    ? escape foo {
    >     print("x")
    >     foo(33)
    >     print("y")
    > } catch p {
    >     print(p)
    >     p + 44
    > }
    # stdout: x33
    
    # value: 77

With this pragma on, for-loops and while-loops benefit from this same 
extension:

    ? for i in 1..10 {
    >     if (i > 3) { break i }
    > } catch p {
    >     p + 0.2
    > }
    # value: 4.2


>> When programmatic recovery based on signalled information is desired, you
>> would have to use case #1. I will argue that this is better structured
>> anyway than dispatching on the type of a thrown exception.
>
>I suspect there are cases where a type hierarchy *is* what's optimal for
>failure handling, in which case not providing such (possibly as a
>library, though) may lead to poor ad-hoc implementations. I have no
>justification for this.

In a later message, Alan Karp suggests likewise. It would be great to have 
an example. 

The ejectors created by the escape expressions, like "foo" above, do form a 
flat taxonomy with somewhat different properties: the categories are only 
dynamic in extent, but they are a dynamic number of categories, whereas the 
"typed exception" style only gives us a static number of categories. Once we 
have some examples, we'll be able to examine pros and cons.


>> Should we adopt this proposal, we will be able to close the last
>> significant outstanding open bug from the Wagner-Tribble review. (The only
>> other open bug is Swing specific, so we'll close it one the move to SWT is
>> complete.)
>
><self-centered>
>The move cannot be considered complete until SWT works on Mac OS X.
></self-centered>

<font-of-wisdom hah="true">
That's a deal. I will leave this bug open until SWT works on the Mac.
</font-of-wisdom>

>>     escape outOfBounds {
>>         ...
>>         def val := collection.lookup(i+j, outOfBounds)
>>         ...
>>     } catch index {
>>         ... index ...
>>     }
>[...]
>This seems an adequate solution for simple cases (except for inter-vat
>use; see below). I have vague feelings that this might not work for some
>cases, and it seems ugly, but I can't justify either.

Btw, I appreciate that you note your esthetic reactions even when you have 
no justification. It's valuable data either way.


>I note that you can't use the collection[...] syntax for a lookup like
>this, since get/2 is already used for specifying a default value.
>
>(I wish E had been designed to use Smalltalk-style method names, because
>it helps distinguish methods with different argument lists that do the
>same thing - and makes more self-documenting code if you choose the
>names right.)

Personally, I like Smalltalk-like method names better too. In fact, I think 
they're a brilliant compromise between the various issues. Unfortunately, E 
strives for familiarity and so can't do this.


>Separate issue: Have you considered making the second argument of get/2
>a thunk?

Since this would be a non-upward compatible change, let's instead give such 
a method a different name; let's say "lookup". Dean points out in a later 
message that the Smalltalk #at:ifAbsent: actually works this way. Our lookup 
example could then be more simply:

     to lookup(index, failThunk) :any {
         ...
         if (...) {
             ^failThunk()
         }
         ...
     }

And a valid use would include calculating the "instead" value:

    collection.lookup(key, thunk{ calculateInstead() })

as well as a variation of the previously explained escape pattern:

    escape outOfBounds {
        collection.lookup(key, outOfBounds) {
    } catch _ {
        # ... handle lookup failure ...
    }

The difference being that since lookup might be invoking a thunk, on lookup 
failure it calls its failThunk with no arguments, so we can ignore the value 
that gets matched against the catch-pattern. ("_" is the "ignore" pattern. 
It matches anything and binds nothing.)


[out of order]
>> This example revealed to be the hole in the above proposal: the use of
>> [extended escape style] does not generalize to any corresponding inter-vat or
>> asynchronous pattern. [...]
>
>Indeed. I was preparing to object vigorously before getting to this part
>of your message.
>
>For my current project, a failure-type-distinguishing system is
>approximately useless if it cannot be used across vats.
[...]
>> * Should be an escape-catch (except that it doesn't work asynchronously)
>
>That 'except' is the most critical problem with this proposal, for me.

Although I don't think I like the following idea, I mention it for 
completeness.

If lookup were instead written as:

     to lookup(index, failThunk) :any {
         ...
         if (...) {
             if (Ref.isNear(failThunk)) {
                 ^failThunk()
             } else {
                 ^failThunk <- ()
             }
         }
         ...
     }

this could also be used to support the remote case. The reason I don't like 
it is that so far it's always seemed like a bad idea to test nearness and do 
something different depending. Instead, it has always seemed that in any 
situation in which you may be dealing with an eventual reference, it has 
always been better to treat it unconditionally as something that may be 
eventual. So no it's my turn to speak only from esthetics without 
justification ;).


>> * A try-catch followed by unsealing the box containing the problem
>
>If there's only one exception-unsealer per vat, this would mean handing
>a potentially very powerful capability to code that shouldn't really
>need it.
>
>However, this could be solved by providing "refraction"
>(http://www.eros-os.org/pipermail/e-lang/2003-February/008464.html).

I had been thinking of a single unsealer per vat, but I think you're right 
to raise the refraction issue. I think it's time to try again to write a 
meta-circular interpreter for E along these lines.


>Lastly, I'd like to mention something interesting in the way of
>exception propagation semantics. I'm not sure this is a good idea, but I
>think it would solve the information leakage problem while allowing
>distinguishable failure types across vats.
>
>In the ColdC language (http://web.cold.org/):

Sounds intriguing, but I don't get it yet. It seems like it may have some 
relationship to the Keeper direction Dean is suggesting.


----------------------------------------
Text by me above is hereby placed in the public domain

        Cheers,
        --MarkM