Yuval Kogman wrote:

>On Thu, Sep 29, 2005 at 13:52:54 -0400, Austin Hastings wrote:
>  
>
[Bunches of stuff elided.]

>>A million years ago, $Larry pointed out that when we were able to use
>>'is just a' classifications on P6 concepts, it indicated that we were
>>making good forward progress. In that vein, let me propose that:
>>
>>* Exception handling, and the whole try/catch thing, IS JUST An awkward
>>implementation of (late! binding) run-time return-type MMD.
>>    
>>
>
>Exception handling is just continuation passing style with sugar.
>
>Have a look at haskell's either monad. It has two familiar keywords
>- return and fail.
>  
>
>Every statement in a monadic action in haskell is sequenced by using
>the monadic bind operator.
>
>The implementation of >>=, the monadic bind operator, on the Either
>type is one that first check to see if the left statement has
>failed. If it does, it returns it. If it doesn't it returns the
>evaluation of the right hand statement.
>
>Essentially this is the same thing, just formalized into a type....  
>

Internally, it may be the same. But with exceptions, it's implemented by
someone other than the victim, and leveraged by all. That's the kind of
abstraction I'm looking for. My problem with the whole notion of "Either
errorMessage resultingValue" in Haskell is that we _could_ implement it
in perl as "Exception|Somevalue" in millions of p6 function signatures.
But I don't _want_ to. I want to say "MyClass" and have the IO subsystem
throw the exception right over my head to the top-level caller.

I guess that to me, exceptions are like aspects in they should be
handled orthogonally. Haskell's "Either" doesn't do that -- it encodes a
union return type, and forces the call chain to morph whenever
alternatives are added. The logical conclusion to that is that all subs
return "Either Exception or Value", so all types should be implicitly
"Either Exception or {your text here}". If that's so, then it's a
language feature and we're right back at the top of this thread.

>>Specifically, if I promise you:
>>
>>  sub foo() will return Dog;
>>
>>and later on I actually wind up giving you:
>>
>>  sub foo() will return Exception::Math::DivisionByZero;
>>    
>>
>
>In haskell:
>
>       foo :: Either Dog Exception::Math::DivisionByZero
>
>e.g., it can return either the expected type, or the parameter.
>
>Haskell is elegant in that it compromises nothing for soundness, to
>respect referential integrity and purity, but it still makes thing
>convenient for the programmer using things such as monads
>
>  
>

For appropriate definitions of both 'elegant' and 'convenient'. Java
calls this 'checked exceptions', and promises to remind you when you
forgot to type "throws Exception::Math::DivisionByZero" in one of a
hundred places. I call it using a word to mean its own opposite: having
been exposed to roles and aspects, having to code for the same things in
many different places no longer strikes me as elegant or convenient.

>>the try/catch paradigm essentially says:
>>
>>I wanted to call <c>sub Dog foo()</c> but there may be times when I
>>discover, after making the call, that I really needed to call an anonymous
>><c>sub { $inner::= sub Exception foo(); $e = $inner(); given $e {...} }</c>.
>>    
>>
>
>Yes and no.
>
>The try/catch mechanism is not like the haskell way, since it is
>purposefully ad-hoc. It serves to fix a case by case basis of out
>of bounds values. Haskell forbids out of bound values, but in most
>programming languages we have them to make things simpler for the
>maintenance programmer.
>  
>

Right. At some level, you're going to have to do that. This to me is
where the "err" suggestion fits the most comfortably: "err" (or "doh!"
:) is a keyword aimed at ad-hoc fixes to problems. It smooths away the
horrid boilerplate needed for using exceptions on a specific basis.

  do_something() err fix_problem();

is much easier to read than the current

 { do_something(); CATCH { fix_problem(); }}

by a lot. But only in two conditions: first that all exceptions are
identical, and second that the correct response is to suppress the
exception.

To me that fails because it's like "Candy Corn": you only buy it at
Halloween, and then only to give to other people's kids.

As syntactic sugar goes, it's not powerful enough yet.

>>We're conditionally editing the return stack. This fits right in with
>>the earlier thread about conditionally removing code from the inside of
>>loops, IMO. Once you open this can, you might as well eat more than one
>>worm. Another conceptually similar notion is that of AUTOLOAD. As a perl
>>coder, I don't EVER want to write
>>
>>  say "Hello, world"
>>    or die "Write to stdout failed.";
>>
>>-- it's "correct". It's "safe coding".  And it's stupid for a
>>whole bunch of reasons, mostly involving the word "yucky".
>>    
>>
>
>It's incorrect because it's distracting and tedious.
>
>http://c2.com/cgi/wiki?IntentionNotAlgorithm
>
>Code which does it is, IMHO bad code because obviously the author
>does not know where to draw the line and say this is good enough,
>anything more would only make it worse.
>  
>

For instance, some bad coders :) wrote:

[quote="http://www.unix.org.ua/orelly/perl/cookbook/ch07_01.htm";]
... implicit closes are for convenience, not stability, because they
don't tell you whether the system call succeeded or failed. Not all
closes succeed. Even a |close| on a read-only file can fail. For
instance, you could lose access to the device because of a network
outage. It's even more important to check the |close| if the file was
opened for writing. Otherwise you wouldn't notice if the disk filled up.

close(FH)           or die "FH didn't close: $!";

The prudent programmer even checks the |close| on standard output stream
at the program's end, in case STDOUT was redirected from the command
line the output filesystem filled up. Admittedly, your run-time system
should take care of this for you, but it doesn't.
[/quote]

The last sentence is telling, I think. The run-time system SHOULD take
as much care as possible. And rub my feet.


>The .resume continuation in the exception is equivalent to the call
>stack, as is any other continuation, implicit or explicit.
>
>  
>

(Found while looking up Haskell docs, above.)

"The continuation that obeys only obvious stack semantics, O
grasshopper, is not the true continuation."
- Guy Steele



>>So <c>resume</c> is a multi, no? (Or it could just be a method:
>>$!.resume, but that doesn't read as well in a block that *really* should
>>be as readable as possible.)
>>    
>>
>
>It can't be a method because it never returns to it's caller
>

True for any method that invokes exit(), no? Or that says NEXT on a
label outside its scope.

> - it's a continuation because it picks up where the exception was thrown,
>and returns not to the code which continued it, but to the code that
>would have been returned to if there was no exception.
>  
>

Hmm, I'm thinking that the exception is an object that has access to a
continuation, and you get that access by invoking <c>resume $e;</c> more
or less.


>>Also, any layer of exception handling may do some nontrivial
>>amount of work before encountering its own exception:
>>    
>>
>
>That's OK.
>
>  
>
>>Which kind of argues that either (A) very fine control of RESUME blocks
>>must be available:
>>    
>>
>
>just assume they work inside out when being resolved. This is just
>recursion into an alternative control flow, which is then stepped
>out of if there were as many .resumes as exceptions were thrown.
>There is no fundamental difference between exceptions and normal
>code once you accept continuations.
>  
>

I was thinking of syntax.

>  
>
>>{...
>>  CATCH -> $e
>>  {
>>    do_some_work;
>>    RESUME {...}
>>    
>>
>
>I don't understand this at all.
>
>What does it mean? what exception is it?
>
>I think maybe this is what you wanted:
>
>{
>       error_throwing_code;
>       CATCH {
>               do_some_work;
>               CATCH {
>                       # the exception raised in the exception handler is
>                       # caught here
>               }
>       }
>}
>
>  
>

Err, no.

The scenario is that I try something (error_throwing_code) and catch an
exception. Then while showing a dialog box to the user, for example, I
get another exception: not enough handles or whatever. So someone higher
than me resolves that on my behalf, then resumes me. I'm still trying to
resume the error thrown earlier:

{
  error_throwing_code();
  CATCH {
    show_dialog_box();
    # ...
  }
}

Now I need to ask, what happens when show_dialog_box throws an
exception? Presumably, I don't catch it in this code path, or there will
be a stack fault shortly.

One possibility is that the catcher of an exception knows little or
nothing about the innards of the thrower. That's the normal, today,
Java/C++ case. Variables only scope inwards. But that pretty heavily
limits the things that can be done with exceptions. So maybe if there is
a resume, I can retry my operation:

{
  error_throwing_code();
  CATCH {
    show_dialog_box();
    CATCH {
      throw;   # Pass the exception
      show_dialog_box(); # Retry if I get resumed?
    }
  }
}

That's going to get old, quickly.


>>sub copy_file($from, $to)
>>{
>>  ...
>>  RESUME -> $r
>>  {
>>    when Resumption::Microsoft::AbortRetryFail::Abort { last; }
>>    when Resumption::Microsoft::AbortRetryFail::Retry { continue; }
>>    when Resumption::Microsoft::AbortRetryFail::Fail    { return FAILED; }
>>  }
>>}
>>
>>I don't actually like this, because I don't like the whole notion of
>>upper-case blocks: it's stuff that the really good perl IDE is going to
>>hide so that the "real" code shows through.
>>    
>>
>
>Exceptions are real code too - especially ones that are being dealt
>with.
>
>It's just slightly more aspect oriented in style.
>
>In fact, the whole point of AOP is to remove what they call cross
>cutting concerns (like logging, persistence, thread safety, etc)
>from the main code to keep it clean, without making the important
>support code invisible. They do this by adding what they call
>aspects from a third person perspective, into the control flow of
>the normal code, by specifying where the aspect gets control in a
>declarative style.
>
>  
>
>>that someone can catch these errors at the relevant place and quickly
>>tailor them. The right place, of course, is the sub or method that is
>>doing the work. I want to be able to CATCH local exceptions locally,
>>too! Not because "I don't know what to do", but in a more Knuth/literate
>>sense, I don't want to write about what to do in-line with my beautiful
>>algorithm:
>>
>>sub greet
>>{
>>  print "Hello, " # or throw PrintException;
>>  print "world\n" # or throw PrintException;
>>
>>  CATCH
>>  {
>>    when PrintException {...}
>>  }
>>}
>>
>>But I want to know which PART of my elaborate greeting failed. In the p5
>>version I could discriminate between first-part failure and second-part
>>failures. Why has p6 backslid into mealy-mouthed genericity?
>>    
>>
>
>It hasn't.. We still have 'or die' for raising exceptions, and you
>can use, like you mentioned
>
>  
>
>>One way, Yuval's suggestion, is to make "err" be a quicky catch mechanism:
>>    
>>
>
>It's not my suggestion, btw ;-)
>
>  
>
Ah, sorry. I conflated the "err" thing with the Exceptuation thing. Mea
culpa.

>>sub greet
>>{
>>  print "Hello, " err die "Could not write first part";
>>  print "world!\n" err die "Could not write second part";
>>}
>>
>>But that's not abstracting anything. It just keeps the same, tedious,
>>check-every-result coding layout and gives it fractionally worse
>>Huffmanization.
>>    
>>
>
>Well, there is a conflict here:
>
>you want to mark and not mark your errors at the same time.
>  
>

Almost. I want exceptions, or exceptuations, to be sufficiently
transparent that I can use (below) some mechanism to infer details of
the problem without having to know too much of the internals below me
(but some, obviously); and I want all except(uat)ions to be potentially
resumable, except that some of them will override the resume
method/multi to avoid this.

But <c>resume</c> should be part of the Exception interface.

>  
>
>>What I want is to catch my errors elsewhere, and still
>>be able to localize their solution. Cleverly, there's already a way to
>>do this: labels. All I need is to know where I'm at, label-wise, and
>>I've got a dandy way to mark my code up.
>>    
>>
>
>This is feasable... I don't see why it shouldn't be implemented, but
>frankly I don't see myself using it either.
>
>This is because the types of exceptions I would want to resume are
>ones that have a distinct cause that can be mined from the exception
>object, and which my code can unambiguously fix without breaking the
>encapsulation of the code that raised the exception.
>  
>

Okay. So one way is to include a continuation in the exception. That
lets you resume, if the exception type is one you know. It lets me
inspect the code tree or call stack or whatever and process it in a
possibly invasive way, possibly resuming. I think we're in sync on this one.

>These are all very specific and reusable, regardless of what we're
>resuming. This is nice because especially if we're trying to e.g.
>
>       my @handles = map { open $_ } @file_names;
>
>we can let the user skip unreadable files, by using return with no
>value instead of returning a handle. This is a generic pattern that
>enhances the user experience by not forcing the user to start over
>again.
>
>For more specific code, i doubt there will be several exceptions of
>the same kind in a call chain.
>  
>
What's your example there? The "err undef" fashion, or the "pops up a
dialog box and then calls setuid()"?

I confess I find that (the dialog box) an amazing piece of work, but in
practical terms somebody someplace has to be doing a retry: I don't see
how it would work via resuming an exception.


>>[quote=http://java.sun.com/docs/books/tutorial/essential/exceptions/finally.html]
>>The final step in setting up an exception handler is to clean up before
>>allowing control to be passed to a different part of the program.
>>    
>>
>
>For cleanup we have real useful garbage collection, with a
>declarative interface to decide on how an object needs to disappear.
>
>Resource management is the task of the resource creator, not
>exception handler, IMHO. It's just too hard to get it right.
>
>So your point:
>
>  
>
>>But resumptions are going to let us FIX the problem that occurred when
>>we tried to open the 256th filehandle. So all the directory parsing,
>>file exploring, and whatnot is going to be *preserved*.
>>    
>>
>
>is very valid
>
>  
>
>>Thus we don't
>>actually want to automatically finalize all that stuff unless we know
>>for sure that we aren't going to resume: even *later* binding.
>>    
>>
>
>Continuations give this behavior consistently.
>
>They just need to be garbage collected themselves, in a way.
>
>What I would like to disallow is the ability to take $! and just
>.resume it out of the blue in code that isn't an exception handler.
>That just does't make sense:
>
>       try { ... }
>       some_other_stuff;
>       $!.resume if $!;
>  
>

Unless Damian does it ;)

>>Yet another use for a Pascal's <c>with</c> keyword:
>>    
>>
>
>I actually quite liked that RFC =)
>
>The problem is that records in a static language are static, and
>hashes are dynamic.
>
>There is no way to tell the user at compile time that a variable
>doesn't exist because we don't know if there's such a hash key.
>
>This means that 'use strict' cannot work in a 'with'ed block.
>
>  
>

This is, to me, different. It's a "discontinuation". The exception
handler is basically stripping the "ation" from the "continue", briefly.
My suggestion here is that <c>with</c> load the dataspace of the
continuation atop the current environment.

The more I think about it, though, the harder it seems. It makes sense
when there's no encapsulation: when you're handling exceptions from
within you own package and don't mind updating member data or whatever.
But for things like smartening up the map {open} example you gave, the
requirement for opacity makes anything more than the "err" suggestion
impractical.

=Austin


Reply via email to