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

Kevin Reid kpreid at attglobal.net
Tue Jun 7 09:48:29 EDT 2005


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.

>> 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.



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'.)



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]))
     } ...


>
>> 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.

>>>> 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.

> 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.

-- 
Kevin Reid                            <http://homepage.mac.com/kpreid/>



More information about the e-lang mailing list