[e-lang] Exception types and ejector patterns (was Re: Remaining semantic issues)

Mark S. Miller markm at cs.jhu.edu
Sat Jul 22 21:07:12 EDT 2006


This is in reply to old e-lang message
http://www.eros-os.org/pipermail/e-lang/2005-June/010708.html
Since it's been a while, I quote from the original in full.

Thanks to Kevin Reid for collecting URLs for some of these old unanswered 
e-lang emails.

Kevin, sorry it's taken so long for me to respond!


Kevin Reid wrote:
> On May 31, 2005, at 22:44, Mark Miller wrote:
>> Kevin Reid wrote:
>>> On May 30, 2005, at 18:46, Mark Miller wrote:
>>>> Kevin Reid wrote:
>>>>> This raises interesting issues in relation to exception types. Most 
>>>>> usages of exception types can be replaced with separate ejectors - 
>>>>> but should they be?
>>>> If the purpose of the exception types would be to enable the caller 
>>>> to dispatch on these types, in order to react differently to 
>>>> different exceptions, then yes, these should be done with ejectors 
>>>> or ejectoids instead.
>>> I disagree. For example, I just recently wrote an interface to the 
>>> POSIX read() system call:
>>>   to read(maxOctets, eofEjector, errorEjector)
>>> There are at least three different errors (not counting EOF) that 
>>> read() may return, ignoring the ones that result from memory errors. 
>>> Does it make sense to write read/5, and require the client to supply 
>>> three almost-but-not-quite-identical ejectors?
>>
>> I think this is a terribly important question. My general experience 
>> is that API designers go to a lot of trouble to distinguish different 
>> error cases, so callers could dispatch on them, but that callers 
>> invariably do something like
>>
>>     if (read(...) < 0) {
>>         perror(...)
>>         // react to a failure to read
>>     }
> 
> Yes, but we don't want to *prevent* clients from doing something nicer 
> than this, which is what we're doing if there is only one ejector 
> parameter and no exception typing or data fields, as your example would 
> suggest.
> 
>> Could you show us some code that dispatches on these errors to do 
>> something useful? A few illustrative examples of successful error 
>> handling, which depend on what error happened, may teach us all a lot.
> 
> I've been looking for such examples all along, and I haven't thought of 
> any.  I think the problem is that this is something which one only really
> finds in Real Applications that do error-prone kinds of operations, and 
> have been found by experience to need to handle particular errors, either:
> 
>   * reporting them to the user with interpretation ("File not found" => 
> "You need to install the Baz module")
> 
>   * handling (<file:baz>.copyFrom(<http://example.net/app/baz>))
> 
> (Note that these are both kinds of things which can be seen as less 
> necessary under "the user is the/a programmer/geek" conditions.)
> 
> Conclusion: We need more examples of Real Applications in order to 
> figure out what we really need for error handling.

Agreed. Perhaps the work around Erlang may be a useful source of examples, 
since Erlang's motivating example was a highly reliable (five nines) telephone 
switch which coped well with both hardware and software errors. In many ways, 
Erlang is an E-like language (or E is Erlang-like). But I don't know that 
their approach to error reporting and recovery is sufficiently similar for 
lessons to translate. Just a thought.


>>> One could argue that since these are ejectors, it should just return 
>>> errno through the ejector, not using exception objects at all; but 
>>> that prevents using the standard optional-ejector tools - 
>>> throw.eject() can't be used to automatically fall back to (sealed) 
>>> regular throwing. It also reduces composability.
>>
>> Perhaps we should adopt a style of accepting optEjectors as named 
>> arguments that default to null? Then, the caller would only need to 
>> mention the ones they care about, and let all the rest get lumped 
>> together into something bad happened: ...
> 
> This is certainly a possibility. I'd like to think, however, that it 
> will rarely be necessary.
> 
> Also, this pattern does not allow for 'lumping all the rest together' 
> into a particular ejector, since they default to null only.

[lumping-or-not discussion continues below, after the following aside]


> Incidentally, I think it might be a good idea to not use null as the 
> 'don't eject, just throw' value, because it requires an explicit 
> conditional by the recipient (even if this is buried in throw.eject most 
> of the time).
> 
> Instead, what if we simply pass 'throw' as the argument? This will work 
> because throw has a run/1 method (though it needs to implement 
> OneArgFunc in Java E), is more informative when reading code, and 
> eliminates the need to check whether 'optEjector' is null - it's not 
> 'opt' any more. (Or we could have a special object just for this 
> purpose, instead of semi-punning 'throw'.)

I don't know why I've been resistant to this. On re-examination, it seems 
reasonable to me, and a better style than use of null. If we do this, I think 
we should use throw itself. I don't see any reason to introduce another object.

During the transition, methods that had accepted a null should continue to do 
so. Let's change all the callers before we change any of the callees.


[lumping discussion continues]

> Whenever the ejected value (whether it's an exception or not) is 
> meaningful, we could use a simple utility to split a single ejection 
> path into several, *if* they are reliably distinguishable - therefore, 
> exception types can be converted into multiple ejector parameters. The 
> reverse is not so straightforward.
> 
>   def ejSwitch {
>     to get(map) {
>       def ejSwitch1 {
>         to run(value) {
>           for guard ? (value =~ coerced :guard) => handler in map {
>             return handler(coerced)
>           }
>           throw(value)
>         }
>         to run() { ejSwitch1(null) }
>       }
>       return ejSwitch1
>     }
>   }
> 
>   escape otherError {
>     escape missing {
>       foo(bar, ejSwitch[Absent => missing,
>                         any => otherError]))
>     } ...

This is a good observation: It's more convenient, for more sound handling 
patterns, if multiple handling cases are lumped together in a single ejector 
which then dispatches on the ejector argument. The cool thing about this 
observation is that the ejector argument type (or whatever information is used 
for the dispatch) is only significant so long as we're ejecting. If it falls 
through and gets thrown instead, then we should no longer expect any 
programmatic dispatch on the type (or whatever) of the ejector argument.

This allows E to seem more conventional regarding programmatic distinctions 
among exceptional cases, while still lumping together all throws into 
"something bad happened". It is also consistent with the stance that, once 
it's thrown rather than ejected, it becomes opaque to catchers.


>>> I think that there are cases where exception typing is even more the 
>>> obviously right solution, that only come up in complex situations - 
>>> and most of the E code written so far is trivial WRT necessary error 
>>> handling.
>>> Also: EIO streams are written to have a single 'terminator' which is 
>>> resolved to a broken reference upon failure. It will surely be useful 
>>> to distinguish types of failure there, and so we need an exception 
>>> type system for that.
>>
>> This sounds plausible, but I'm skeptical. Again, I'd like to see some 
>> illustrative examples where code would actually use such type 
>> information to do something useful.
> 
> As I said above, I think we need to make, or ponder thoroughly, a Real 
> Application in order to do this.

Yes. Other candidates would be great.


>>>>> Also, we need some way for E-defined objects to throw exceptions 
>>>>> with custom data fields, for when the failure needs to be described 
>>>>> for code that intends to handle it.
>>>>
>>>> Not if failures which should be handled are instead done with 
>>>> ejectors or ejectoids rather than normal exceptions.
>>> Again, I think that these features are critical in some cases, and we 
>>> shouldn't throw them out just because we can do the trivial cases in 
>>> other ways.
>>> (Also, your statement seems to me to to be confusing varieties of 
>>> object (exceptions) with varieties of nonlocal exit (throw()).)
>>
>> How's that?
> 
> I meant that the use of exception *objects* does not require that there 
> be any throwing involved. Your statement above implied that the ejectors 
> would not be using exceptions as arguments.

Yes, that's an interesting question. In order to make your ejSwitch idea above 
work, the ejector argument might get thrown, and so must be the kind of thing 
that could be, or be included in, an exception. Given this constraint, I 
suppose it may as well be an exception. On E-on-Java, it would currently be 
hard to enable E programs to define new exception types. But if we decide 
that's the right thing, we could certainly do it.


>> If the code that would be reacting to an exception is going to 
>> dispatch on the exception type, then this dispatch turns the 
>> distinctions back into flow control.
> 
> I don't understand how this is related, but:
> 
> If you have an exception, you can pass it on by itself to some handler 
> after receiving the exception. If you have something that takes three 
> ejector parameters, you have to have the handler provide you three 
> ejectors beforehand. If you have three control flow paths, you need to 
> invent distinguishing values to pass to the handler.
> 
> This is what I meant above by reducing composability.

Yes. When I wrote the comment above, I was indeed confusing "exception" with 
"thrown exception". Once an exception is thrown, I remain skeptical about 
whether it would then be useful and robust to dispatch on its type. However, 
as you point out, dispatching on its type while we're still ejecting is useful 
and robust, and having it be an exception so that we can fall back to throwing 
it is also useful.

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

     Cheers,
     --MarkM



More information about the e-lang mailing list