wanna_code_perl has asked for the wisdom of the Perl Monks concerning the following question:

I'm converting a small (~200 SLOC) OO module to use Mouse, for some comparative benchmarking and other educational tomfoolery. I wrote it originally with manual OO. Nothing fancy. But I'm not sure how to implement my existing error handling mechanism with Mouse::Util::TypeConstraints. My error handler is configurable to either carp(), or set $obj->error string, or both. As far as I can tell, M::U::TypeConstraints croak()s on everything and that's it. So, going from this:

# Previous (manual OO) code: sub error_mode { + my ($s, $mode) = @_; + if ($mode) { + $s->_err('Invalid error_mode', $s->{error_mode}) + unless $mode =~ /^(carp|error|both)$/; + $s->{error_mode} = $mode; + } + + $s->{error_mode}; + }

... to this:

# Converted to Mouse: enum 'ErrorMode' => qw<carp error both>; has 'error_mode' => ( is => 'rw', isa => 'ErrorMode', default => 'erro +r' );

... this works in the sense that $obj->error_mode($new_mode) correctly sets the mode, but if $new_mode is invalid, it outputs a fatal mess like this:

Attribute (error_mode) does not pass the type constraint because: Vali +dation failed for 'ErrorMode' with value at /usr/local/lib/x86_64-li +nux-gnu/perl/5.26.1/Mouse/ line 395. Mouse::Util::throw_error(Mouse::Meta::Attribute=HASH(0x56165c0 +b5528), "Attribute (error_mode) does not pass the type constraint bec +a"..., "data", "", "depth", -1) called at ./t/05-errors.t line 16

I know I can use message to have a slightly more readable error message, but the main problem is of course the fatal exceptions with no way that I can see to implement any other kind of error handling. Of course I know I don't have to use Mouse::Util::TypeConstraints, but then it starts to look identical to my manual OO code. That or I guess I could wrap the accessor/mutator with a method that does eval { $self->_error_mode(@_) } and handles the errors from there, but that's, well, gross, especially given there are half a dozen methods to do this with. Is there a middle ground?

Replies are listed 'Best First'.
Re: Non-fatal error handling with Mouse types?
by daxim (Curate) on Oct 02, 2019 at 11:18 UTC
    In theory it should be possible to use simple OOP to override the behaviour you do not want. However, I do not know how to get around the problem that the derived class does not export anymore.
    package Mouse::Util::TypeConstraints::CarplessErrors { use Mouse; extends 'Mouse::Util::TypeConstraints'; sub throw_error :method { my ($self, $message, %args) = @_; warn $message; } } package Foo { use Mouse; use Mouse::Util::TypeConstraints::CarplessErrors; enum 'ErrorMode' => qw<carp error both>; has 'error_mode' => ( is => 'rw', isa => 'ErrorMode', default => ' +error' ); } Foo->new->error_mode('asdsdf');


      This looks like a reasonable approach. I'll give it a try.

Re: Non-fatal error handling with Mouse types?
by tobyink (Canon) on Oct 03, 2019 at 08:08 UTC

    Use coercion.

    use Types::Standard qw(Any Enum); has error_mode => ( is => 'rw', isa => (Enum[qw/carp error both/])->plus_coercions(Any, q{'both'} +), # default to 'both' coerce => 1, );

    (Coercions can also be done with native Mouse types, but that requires more lines of code, and I can't remember how off the top of my head.)

Re: Non-fatal error handling with Mouse types?
by thezip (Vicar) on Oct 02, 2019 at 20:51 UTC
    Hey y'all,

    I would just like to comment that it is refreshing to be able to see differing opinions and solutions that have *NOT* caused a discussion to spiral into a flame-war.

    Thank you gentlemen for keeping the argument civil!


    *My* tenacity goes to eleven...

      BUT WHOSE SIDE ARE YOU ON, TRAITOR?!?!?11!?111!/?¿

Re: Non-fatal error handling with Mouse types?
by 1nickt (Abbot) on Oct 02, 2019 at 10:55 UTC

    Hi, all the type-validation tools I know raise an exception when the value doesn't pass, and I can't say I've ever needed anything else. How would `error_mode` ever be handed a bad value, since it's only called internally? Would there ever be a case where your code passed a bad value and you didn't want it to croak?

    Hope this helps!

    The way forward always starts with a minimal test.

      I think the issue is more with Moo*'s infatuation with Java-like error backtraces, which are rarely helpful. In most cases you want the bottom-level call within the code you're working with, not the full call stack in the maze of anonymous subroutine helpers that the Moo* family is so fond of.

      Now that I talk about it, maybe a judicious application of Carp's %Carp::Internal could help clean up those tracebacks to a more manageable state?

      #!perl use strict; package foo; use Mouse; use Mouse::Util::TypeConstraints; # Converted to Mouse: enum 'ErrorMode' => qw<carp error both>; has 'error_mode' => ( is => 'rw', isa => 'ErrorMode', default => 'erro +r' ); package main; use strict; use Carp; $Carp::Internal{ 'Mouse::Util' } = 1; $Carp::Verbose = 0; my $foo = foo->new( error_mode => 'silent' );

      This eliminates at least Mouse::Util from the list of the backtrace. Most likely, more of these need to be added...

        Thanks. Indeed, glad I wasn't the only one wondering what the point of those backtraces was. $Carp::Internal would certainly help clean that up a bit, and that's helpful. It's looking like I'd probably also have to do something along the lines of daxim's approach to override Mouse::Util::TypeConstraints->throw_error to make the errors non-fatal.

      $obj->error_mode() would in fact be called externally, and there are several such similar setter subs. The trivially alliterative answer to your second question is "yes". I love exceptions, too, but they were not the best choice for this project.

        If when a consumer of your class calls a function with a bad value, you don't want to raise an exception, then I don't understand why you are using type constraints.

        The way forward always starts with a minimal test.
Re: Non-fatal error handling with Mouse types?
by Haarg (Curate) on Oct 03, 2019 at 10:02 UTC

    If this is something you want to do on a number of attributes, you could use a method modifier to wrap them with exception handling. Here's an example:

    use strict; use warnings; BEGIN { package Foo; use Moo; use Types::Standard qw(Enum); has error_mode => ( is => 'rw', isa => Enum[qw/carp error bot +h/], default => 'error' ); has another_attribute => ( is => 'rw', isa => Enum[qw/doing a thing/ +], default => 'a' ); sub _err { my ( $self, $err ) = @_; warn $err; } around qw(error_mode another_attribute), sub { my ($orig, $self) = (shift, shift); my $return; eval { $return = $self->$orig(@_); 1; } or do { $self->_err($@); return $self->$orig; }; return $return; }; 1; } sub welp { my $f = Foo->new; $f->error_mode("blah"); $f->another_attribute("blah"); } welp();

    This would allow you to define all of your attributes using standard Moo* mechanisms, but convert a selection of them to 'soft' error handling.