Carp::Object, an object-oriented replacement for Carp and Carp::Clan

The new Carp::Object module is an object-oriented replacement for Carp or Carp::Clan. What is the point ? Well, here is some motivation.

The Carp module and its croak function have been around since perl 5.000. Errors can then be reported from the perspective of where the module was called, instead of the line where the error is raised. This excellent example from Mastering Perl explains why this is useful :

1	package Local::Math {
2	  use Carp qw(croak);
3	  sub divide {
4	    my( $class, $numerator, $denominator ) = @_;
5	    croak q(Can't divide by zero!) if $denominator == 0;
6	    $numerator / $denominator;
7	  }
8	}

Dieing at line 5 would be meaningless : it would report a division by zero at line 5, but the reason for that is not at line 5, it is in the calling program where a 0 value is passed as $denominator to the divide method. When using croak instead of die, all stack frames within the Local::Math package are skipped, and the error is reported at the caller’s level.

So far so good, but what happens if package Local::Math is just an internal member of a bigger family? Imagine we have a Local::ScalarOps package which delegates some operations to Local::Math, including the divide method. Then croak will report the error at the Local::ScalarOps level, not at the original caller’s level. 

Fortunately, there is a way to tell Carp to ignore other packages as well :

  use Carp qw(croak);
  our @CARP_NOT = qw(Local::ScalarOps Local::Foo Some::Other::Package);

This is much better. The @CARP_NOT array can even be locally modified if some places in the module need a different croaking behaviour. However, if your family of modules is large, it can be cumbersome to enumerate them all, and there is a risk to omit to upgrade the list if you later add new modules to the family.

Carp::Clan offers a solution : the family (the “clan”) of modules can be described through a regexp :

  use Carp::Clan qw(^(Local::|Some::Other::Package));

The regexp is passed as string within the import list. A croak function similar to Carp::croak is implicitly exported (together with other functions carp, confess and cluck). This can flexibly handle clans of modules in a common namespace. But now the clan of modules is specified at compile time : it cannot be dynamically modified. This is probably good enough for most common uses, but for edge cases it can be an annoying limitation.

So here comes Carp::Object : at each croaking site you can tune various aspects of the croaking behaviour (rules for skipping stack frames, customized callbacks for rendering stack frames, etc.) :

  use Carp:Object ();
  my $carper = Carp::Object->new(%options);
  ..
  check_good_condition() or $carper->croak(“there is a problem”);

Possible options are described in the documentation. Internally, stack frames are rendered through Devel::StackTrace, so all Devel::StackTrace options are also available.

Admittedly, the object-oriented idiom is more verbose, but if you prefer there is also a functional API compatible with traditional Carp for hiding the details.

For a complete use case, here is the situation that initially motivated this work. Consider a middleware framework like DBIx::DataModel, an object-relational mapping (ORM) framework that sits between the DBI layer and the application layer. The ORM uses a whole clan of modules in namespaces DBIx::DataModel::* and SQL::Abstract::*. But this is an extensible framework, so clients may well supply their own classes in-between, like for example a custom My::Local::Statement that extends the DBIx::DataModel::Statement class. Therefore the whole clan of modules can only be known at runtime, when the DBIx::DataModel::Schema is instantiated. With Carp::Object we can tune the croaking behaviour so that all ORM modules, including user-supplied modules, are skipped when a SQL error occurs. Details are in the _handle_SQL_error method.

Finally, Carp::Object also offers two additional improvements over Carp or Carp::Clan :

  1. if the croak method receives an exception object instead of a plain string, it just stringifies the object and then behaves as usual. By contrast, Carp or Carp::Clan do not know how to handle exception objects : they just die without performing the ordinary stack frames mechanics. This can be quite annoying when your module doesn’t know what kinds of errors can be raised by the underlying layers.
  2. Carp::Object attempts to infer if some calls in the stack are method calls instead of subroutine calls. When this is the case, the message line for that stack frame is rewritten to make it apparent that this is a method call.

I hope that Carp::Object can be useful to you. Do not hesitate to raise issues if you find bugs.

1 Comment

Very cool! And, someone remembered something I wrote almost 20 years ago. :)

Leave a comment

About dami

user-pic I blog about Perl.