Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Need a substitute for Perl 5 die with newline for raising end-user errors? #59

Open
treyharris opened this issue Jul 6, 2019 · 4 comments
Labels
language Changes to the Raku Programming Language

Comments

@treyharris
Copy link

In Perl 5, die’s message could be terminated with a newline to suppress diagnostic messages (line number at minimum, up to full backtrace if you ask for it—in this issue I’ll just call it “backtrace” for simplicity, whatever it is—it’s anything added by the language to the message given to die).

This suppression via newline is no longer possible in Perl 6 (and for very good reasons that I shouldn’t have to mention here).

It does leave an open question, though: in directly interactive executables, how should the programmer idiomatically handle end-user errors such as incorrect usage (apart from USAGE itself, of course)—things like attempting an operation the user doesn’t have access to, trying to run a graphical program in a non-graphical context, etc.? It seems like the choices are:

  1. Use die anyway and put up with the backtrace printed to users.

    • Pros:
      a. It’s easy
      b. It’s familiar
      c. It doesn’t need special-casing
    • Cons:
      a. A backtrace from a program in a non-debugging context, especially inside error-checking code, is user-unfriendly because it’s ugly;
      b. It’s also user-unfriendly because it buries the useful part of the message at the top of a lot of debugging cruft that is not useful to the end-user
      c. For the programmer, it makes it somewhat more difficult to do a CATCH-all at the top level for production code that turns the language backtrace into something that can be communicated by nontechnical users to support, since user errors are not something that needs such special handling
  2. Follow the method recommended in die’s docs: create a top-level CATCH-all which changes the top-level uncaught behavior so it doesn’t print the backtrace, and then use die.

    • Pros:
      a. It’s simple
      b. It’s consistent
    • Cons:
      a. It requires boilerplate that may not be obvious to many programmers who won’t think to look at die’s documentation and will contrive a solution with exit instead.
      b. For executables I’ll call “targeted at the semi-technical user”, it’s common practice to exit with nice-human messages for things that the program checks, but to “toss its cookies” with a backtrace if it hits an exception unaccounted for. Besides being most parsimonious for the programmer, this seems like very reasonable behavior for users who are likely to know the difference between most “my fault” problems and “bugs”—and a top-level CATCH-all makes this more difficult to be the default behavior.
  3. Create an exception class (X::UserMessage?) that (along with its subclasses) restores the Perl 5 die-with-newline behavior; if it reaches the top level, only the message of the exception is printed, and not a backtrace.

    • Pros:
      a. It’s consistent with most of the rest of exception handling and would easily extend to Failures as well;
      b. It could promote the use of proper exceptions instead of ad-hoc stringy exceptions, which may be desirable for code like we’re talking about (notably, it makes i18n and l10n for messages that may be expected to be seen by non-technical users much easier, since it’s confined to one place).¹
      c. Most CPAN code won’t have to worry about this class of exception at all, since it isn’t concerned with end-user exceptions
      d. You can convert any non-X::UserMessage exceptions into one complete with instructions for reporting bugs with a simple CATCH at the top level that catches everything but X::UserMessage.
      e. Following from the above, it makes the “semi-technical user” oriented behavior of nice messages for things you want to gently inform the user about, but “cookie tossing” for unexpected failures, the normal case without any special handling.
    • Cons:
      a. It seems a little magical.
      b. It doesn’t allow for a simple CATCH-all at the top level with no special-casing—but as mentioned in the last Pro point, it obviates the CATCH entirely in the most likely use case where one would do that.
  4. Create a new word (maybe exitwith) to give some syntactic sugar to

     -> $msg { note ~$msg andthen exit 255 }
    
    • Pros:
      a. It’s closest to Perl 5’s die-with-newline behavior
      b. It’s simple
      c. It’s easy
    • Cons:
      a. It requires a new word
      b. It promotes ad-hoc stringy error messages, making i18n/l10n of messages more difficult
      c. It’s not as easily caught (and by spec, isn’t necessarily catchable at all?).
      d. Since it’s not as easily caught, it will prevent finalizers in ways that a special exception class wouldn’t.
  5. (Throwing this in for completeness:) Adopt the Perl 5 behavior of paying attention to a trailing newline. I mention it, but it seems (to me) patently ridiculous behavior for Perl 6. A not-so-crazy alternative, however, would be to add to die a backtrace-suppression boolean flag (with an accompanying boolean field in exceptions, I presume?) that would stop the backtrace if and only if the exception gets all the way to the top level uncaught.


¹ I don’t think the promotion of proper exceptions is anything to sneeze at; I suspect that for programmers who use stringy ad-hocs for convenience’s sake, they probably have a very large overlap with programmers who only `die` at all for things they actually check for. So making this class of thing easier with real exceptions than with strings would be a win for non-ad-hoc exceptions.
@AlexDaniel AlexDaniel added the language Changes to the Raku Programming Language label Jul 6, 2019
@treyharris
Copy link
Author

Personally, I like the X::UserMessage solution best, though I’m not the greatest at naming things—the concept, not the name. If others like it, I’m happy to draw up a proposed class spec and changes to the docs with example code to show what it would look like in action.

@treyharris
Copy link
Author

On #perl6-dev this was discussed just now at some length, and a couple things to note:

  • There’s an &*EXIT that can be reset as necessary—useful for cases where you aren’t running as a POSIX process in your own right. But it can’t affect the error output, which is the primary issue here.

  • exit itself is a multi defined on Int:D, so one way to skin this cat that could be done entirely through a CPAN module (which isn’t possible for something that would need to reach out into the top-level and install a CATCH) would be to have variants of exit on Str and Exception. Unless someone else has strong feelings about a different option I may try to hack that up and see how it feels.

    It would mean that instead of doing die for user-oriented errors, you do

exit "This is not a graphical display, I can't draw anything!";
# or, equivalently,
exit X::UserError::NoGraphics.new();

and it would note before redispatching to exit 255. Perhaps a new global is needed so that you can influence where fatal messages go rather than using note explicitly?

But even as now stands, assuming these exit variants existed, then, for example, a single program that can run in a CLI or a GUI could deal with this by redirecting $*ERR (perhaps via a filehandle-like buffer) and changing &*EXIT so instead of writing to standard error and exiting, you got a fatal dialog box with the message.

@CurtTilmes
Copy link

I don't like exit since it breaks my LEAVE phasers. (I'd love for that to change, btw. Perhaps that should be a separate issue -- What is a good way to finish any open LEAVE phasers, then exit the process with a non-zero code?)

I've been making a CATCH in a proto MAIN (so it works for all my multi MAINs) that prints the way I want for users.

@treyharris
Copy link
Author

@CurtTilmes It’s hard to get the CATCH clause right, though—a small one (such as is documented in the die docs) doesn’t work right in some circumstances, and a general one is quite long and hairy. (It feels to me analogous to how hard it was in Perl 5 to write while (<>) { ... } from scratch if you wanted to tweak it slightly.)

With the X::User class hierarchy with different top-level handling, I’m advocating the implicit behavior to obviate an explicit CATCH in almost all cases.

I hear you with regards to exit, but I don’t see an alternative that can be done via a CPAN module, do you?

@lizmat lizmat unassigned jnthn Oct 5, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
language Changes to the Raku Programming Language
Projects
None yet
Development

No branches or pull requests

4 participants