Skip to content

Instantly share code, notes, and snippets.

@Ovid
Last active March 21, 2022 06:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ovid/5205534f7dcc52e4d931aaff301b39aa to your computer and use it in GitHub Desktop.
Save Ovid/5205534f7dcc52e4d931aaff301b39aa to your computer and use it in GitHub Desktop.
Exceptions in Perl?

Preface

This is something that likely cannot be made into an RFC for the Perl language at this time because implementation would be greatly simplified when the Corinna object model is in core. For example, a base class for what is discussed might look like the following:

# Exception is a poor name for warnings, so a better name is warranted
class Exception :version(v0.1.0) {
    # $message and $description might be from a messaging role
    field $message     :reader :param;
    field $description :reader :param { "" };
    
    # must not be lazy because stack frames might not be available later
    field $stack_trace :reader { $self->build_stack_trace };
    
    method build_stack_trace :private () {
        # generate stacktrace
    }
    
    method throw () {
        die $self->to_string;
    }
    
    method to_string () {
        return join "\n", $message, $description, $stack_trace;
    }
}

Exceptions

Here's a common problem in Perl:

try {
    $object->some_method;
}
catch ($error) {
    if ( $error =~ /File doesn't exist/ ) {
        ...
    }
    else {
        croak($error);
    }
}

And some programmer sees the File doesn't exist string in the code and changes to the more standard File not found, but misses error handling that's based on string matching.

But exceptions make this safer:

try {
    $object->some_method;
}
catch ($exception) {
    if ( $exception isa Exception::FileNotFound ) {
        ...
    }
    else {
        $exception->throw;
    }
}

With that, you're testing the exception class, something far safer. In fact, at this point, you can localize exception strings. In French, you might get « Fichier introuvable » which, for a French speaker, might be much easier to understand. This wouldn't change the class of the exception, so it's more maintainable.

Exception objects, of course, could have a message, a more verbose description, a stacktrace, and so on.

Exception Hierarchy

What would an exception hierarchy look like in Perl, though? Would it be a tree or a graph (inheritance versus roles). I'm assuming Perl wouldn't handle "checked" exceptions (exceptions that perl requires be trapped at compilation time), but there would still need to be some organization.

For example, in Java, we have both Error and Exception, separate related classes, each of which inherits from Throwable. An Error is thrown when there's an unrecoverable error in a program, such as an AbtractMethodError where a subclass has failed to override an abstract method in a parent class. Even though code might trap an Error (for logging or sending email, for example), it should still probably rethrow that error.

An Exception, on the other hand, is an error that might be recoverable. A FileNotFound exception might cause the program halt, or it might prompt the user to manually enter values that should be in a config file.

However, we can't blindly port over an exception system from other languages. This is Perl, after all. Just reading through the Java error/exception hierarchy makes it clear that much of it doesn't apply to a dynamic language.

But what about this case? (A common issue with tests)

my $warning = capture_stderr {
    $object->some_method;
};
like $warning, qr/$some_warning/, 'We have our expected warning';

And someone changes the warning message and tests fail (guess what I've had to work on yesterday?) What if warnings were objects?

my $warning = capture_stderr {
    $object->some_method;
};
ok $warning isa Warning::Category, 'We have our expected warning';

So actually, we might want a generic messaging mechanism (localizable, I should think) that can be broadly applicable to many problem spaces, or we might want warning objects which are not throwable, though you could probably promote them to an exception and throw them.

And that got me thinking.

Exception Levels

If you use logging software in Perl, you often find "log levels". In the excellent Log::Log4Perl module, here are the log levels:

$logger->trace("...");  # Log a trace message
$logger->debug("...");  # Log a debug message
$logger->info("...");   # Log a info message
$logger->warn("...");   # Log a warn message
$logger->error("...");  # Log a error message
$logger->fatal("...");  # Log a fatal message

I couldn't help but think that "exception levels" might be a better way to think about an exception hierarchy (at which point, the word "exception" doesn't seem appropriate), but now things get interesting. Imagine this:

use exceptions 'warn';

sub first () {
    info "This is a no-op due to the exception level";
}

sub second () {
    warn "This is trappable, but untrapped, prints this message";
}

sub third () {
    error "This is trappable, but untrapped, prints this message, a stacktrace, and dies";

sub fourth () {
    fatal "This is probably trappable (?), but untrapped, prints this message, a stacktrace, and dies";
}

No, I am not suggesting the above syntax (too many clashes with existing Perl). Further, I can see pitfalls with this (easy to swallow warnings), but I wanted to start a discussion about a conceptual framework of different types of informational messages that the program might emit.

All of the above might consume a "Message" role to make it easier to localize messages for various spoken languages. Said role might even be more generally useful outside of exceptions, though this might be unworkable due to the need to maintain translation files. Careful thought might be needed here.

Corinna

Many things for the above would be easier to implement when the Corinna object system gets into the Perl core (work on this is expected to begin after the next major release).

Conclusion

Currently, Perl has no native way of generating exception objects and the CPAN has tons of competing different implementations. Proper exceptions can offer a clean standard, make code more robust, but the dynamic nature of Perl suggests to me that we might want to think about exceptions in a new way.

@duncand
Copy link

duncand commented Mar 9, 2022

Addendum, or similar to with exception objects, you could choose to have one notification channel, and messages are objects of some hiararchy, like exceptions have a hierarchy. Rather than separate named channels, there's one channel and readers can just filter on what message classes they are interested in, either individually or on any point in the hierarchy to include subtypes etc.

@duncand
Copy link

duncand commented Mar 9, 2022

Further to the last 2 points, Perl could be configured that whenever it throws an exception object it ALSO posts the same exception object to the notification queue. So exceptions and notifications are separate concepts but can easily be used together.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment