2010-08-06

Why Am I not using Perl 6 Yet?

I'm not here to deride it, I think its pretty, the syntax is nice, and it lacks some of the annoyances I currently have with Perl 5. Its got great features, and I whole heartedly want them to keep on trucking with that project.

My problem is not a petty squabble over things like Hurr, not perl5 enough or Derp, uses too much rams!, or Its too slow! or qualms about its completedness or its buggyness.

To a pragmatic person, none of those things really matter that much, you have to be doing really heavy work for speed and ram to be a problem on a Modern machine, and for a lot of things, I could not care less if startup time was a WHOLE SECOND LONGER. Hell, the total amount of time spent bitching about load time and speed now, in the real world, is likely to exceed the net total amount of time spent actually waiting for Perl6 to start. And the volumes of text and debate on this issue is almost certain to be a much larger waste of memory ( considering how much a single bit of information is replicated everywhere, and how it has to be replicated just to be *read*, and all the transport stuff that makes that possible ).

Back on the subject!

I think my biggest reason for not using Perl6 yet is that I'm not using Perl6 yet. I guess this is somewhat circular reasoning. but the problem is when I think "Oh, I have a task to achieve", my brain instantly starts forming it with regard to Perl5 and its idioms and methods.

Additionally, When I use Perl5, I'm not really spending a great deal of time messing around with its syntactical nuances. What I'm spending more time doing, is importing and using code and modules that already exists. I have a good mental understanding of all those great Perl modules from CPAN, and which ones I can JustUse to do whatever it is I want to be doing.

When I want to be doing something I don't already know how to do, the first thing I'm hitting up CPAN to see if somebodys done it already in a way I need, or to see if there are a few aggregate parts I can scrape together to make what I want

Also, most of my coding these days revolves around my various Perl5 modules, enhancing, maintaining, etc, and all this of course requires Perl5 to be employed. Its silly to consider depending on Perl6 to make a Perl5 module. And although I know I probably should be helping to reduce this problem by making Perl6 ports of my modules, its a bit chicken-egg because many of my modules are extensions for other Perl5 modules.

So, essentially, going Perl6 would require me to basically throw out everything I know, and then resort to doing things myself? If this is not the case, I don't understand/see how else I'm expected to do something in Perl6.

There's lots of fun examples of people doing raw hacking in Perl6, but I don't see boatloads of people using modules, and I don't see boatloads of Perl6 modules on CPAN when I'm searching for things I need to do.

If there's a secret second c6pan somewhere I'm just not seeing that these magically awesome Perl6 modules are being served on instead, Somebody should post a link to somewhere I'm likely to stumble over it.

Because presently, the gut reaction is barely better than suggesting I move back to PHP, where I have to reinvent every wheel myself in the event my behaviour is not implemented by a core PHP feature.

And the idea of being stuck back in that mindset is less than inspiring to me.

What would it take me to switch?

In a nutshell:
  • A much more obvious path to adoption
    • Obvious path to learning core syntax
    • Obvious path to finding extensions/modules
  • A More Comprehensive Archive of Perl6 modules.
  • Being things I currently use available on Perl6 in similar ways to how they are now, so I can jump ship, and start using those versions instead, and then start hacking on/improving those things with my own modules.

2010-08-04

Extending Exception::Class in Moose

I recently had the joyous experience of porting some code to use proper Object Oriented Exceptions, and found a few niggles in my experience.

Exception::Class is a great module, and in terms of an Exception base class does lots of the things I want an exception module to do.

However, it has one and only one really big problem from my perspective, and that is, by default, its extensibility is a bit limited.

It appears to be highly targeted for its in-line declarations at import(), as follows:

 use Exception::Class (
      'MyException',

      'AnotherException' => { isa => 'MyException' },

      'YetAnotherException' => {
          isa         => 'AnotherException',
          description => 'These exceptions are related to IPC'
      },

      'ExceptionWithFields' => {
          isa    => 'YetAnotherException',
          fields => [ 'grandiosity', 'quixotic' ],
          alias  => 'throw_fields',
      },
  );

This is handy, For the simple case. But it doesn't do you a whole bunch of favours. Adding custom methods is a bane, and there's no field validation/processing support.

The best alternative to getting custom methods is Exception::Class::Nested which lets you do this:

        use Exception::Class::Nested (
                'MyException' => {
                        description => 'This is mine!',

                        'YetAnotherException' => {
                                description => 'These exceptions are related to IPC',

                                'ExceptionWithFields' => {
                                        fields => [ 'grandiosity', 'quixotic' ],
                                        alias => 'throw_fields',
                                        full_message => sub {
                                                my $self = shift;
                                                my $msg = $self->message;
                                                $msg .= " and grandiosity was " . $self->grandiosity;
                                                return $msg;
                                        }
                                }
                        }
                },
        );

This is loads more practical, merely by eliminating the isa => stuff and adding of custom methods, but it still lacks many things in extensibility. No Type checking, no parameter processing, and worst of all, no apparently logical path to avoid clobbering parent methods ( I'm entirely assuming the ->SUPER:: stuff works, but I dislike that peskyness with a passion ). And last but not least, that module won't even install or pass its own tests.

So, you find yourself to this sort of thing:

use strict;
use warnings;
package ExceptionWithFields;
use base 'YetAnotherException';
# Every time I have to do this, I forget how to do it
# which is especially annoying as its not documented anywhere
# and Exception::Class bolts it on to its generated exceptions during ->import()
# so the method is nowhere to be found in Exception::Class::Base 's code 
# or its inheritance hierarchy.
# the inner guts of it are hidden away in Exception::Class::_make_subclass
sub Fields {
     # return an array of field names or they won't get populated.
     return ('grandiosity', 'quixotic');
}

# yes, you have to write your own accessors
# Exception::Class->import() generates these accessors manually.

sub grandiosity { 
    my ( $self ) = shift;
    return $self->{grandiosity};
}

sub quixotic {
    my ( $self ) = shift;
    return $self->{quixotic};
}

sub full_message {
    my $self = shift;
    my $msg = $self->message;
    $msg .= " and grandiosity was " . $self->grandiosity;
    return $msg;
}

1;

YUCK!. . That's an awfully lot of nasty boilerplate :(.

This is only a simple example, so you can see how it'd get more complicated with more advanced things, I don't even want to contemplate how to handle parameter coercion/processing.

So, lets Moose this thing up!

I'm addicted to this Moose thing.

Moose probably makes Exception classes overweight, but considering how short lived they are, in many cases it doesn't really matter.

Unfortunately for us, Exception::Class uses some other weird thing which makes bolting stuff on to it a bit harder.

But fortunately, there is MooseX::NonMoose which makes this mostly painless.

use strict;
use warnings;
package MyException;
use Moose;
use MooseX::NonMoose;
use namespace::autoclean;
extends qw(Exception::Class::Base);
# This method is needed to delete things which are supposed to be handled by Moose 
# so they don't get passed to the parent constructor, because excess args cause it to fail -_-
sub FOREIGNBUILDARGS {
  my ( $class, %args ) = @_;
  for ( $class->meta->get_attribute_list ) {
    delete $args{$_};
  }
  return %args;
}
# Handy addition for giving back traces to user-land.
around show_trace => sub {
  my ( $orig, $class, @rest ) = @_;
  return 1 if exists $ENV{MYEXCEPTION_STACKTRACE} and $ENV{MYEXCEPTION_STACKTRACE};
  return $class->$orig(@rest);
};
__PACKAGE__->meta->make_immutable;

Yay. Suddenly we have something Moose friendly that JustWorks as we want it to. And we've already added functionality by making all our children's stack-traces forced on by an ENV option, but otherwise behave as usual.

Now for the derivative classes

use strict;
use warnings;
package YetAnotherException;
use Moose;
use namespace::autoclean;
extends 'MyException'; 
__PACKAGE__->meta->make_immutable();
1;
use strict;
use warnings;
package ExceptionWithFields;
use Moose;
extends 'YetAnotherException'; 
use namespace::autoclean;

has 'grandiosity' => ( isa => 'Str', is => 'ro', required => 1 );
has 'quixotic' => ( isa => 'Str', is => 'ro' , required => 1 );

# Now with inheritable message code =)
around full_message => sub {
    my ( $orig, $self , @args ) = @_;
    my $msg = $self->$orig( @args );
    $msg .= " and grandiosity was " . $self->grandiosity;
    return $msg;
};

# Stick some lines *after* the stacktrace =D 
around as_string => sub { 
    my ( $orig, $self , @args ) = @_;
    my $msg = $self->$orig( @args ); 
    $msg .= "\n\n Please refer to the ExceptionWithFields Manual for more information"; 
    return $msg;
}
__PACKAGE__->meta->make_immutable();

WAAAY More fun. Waaay Less headaches. Moose++

#!/usr/bin/perl
use ExceptionWithFields;

ExceptionWithFields->throw( 
    message     => "This is a test",
    grandiosity => "This is grand!",
    quixotic    => "Very!",
);