Telephone +44(0)1524 64544
Email: info@shadowcat.co.uk

Shaving a Moose

Announcing MooseX::Antlers

Wed Dec 9 20:40:00 2009

Digg! submit to reddit Delicious RSS Feed Readers

At London Perl Workshop, I got to present MooseX::Antlers - slides are here and the gitweb (soon to be Gitalist, I hope) is here. To actually play, you'll need a branch of Moose as well -

  cpan Class::MOP # will need latest

  git clone git://git.shadowcat.co.uk/gitmo/Moose.git

  cd Moose
  git checkout origin/topic/accessors_not_capturing_meta
  perl Makefile.PL
  make test
  make install

  cd ..
  git clone git://git.shadowcat.co.uk/gitmo/MooseX-Antlers.git

  cd MooseX-Antlers

... and yes, what's currently there is of interest really only to Moose internals hackers. And insane people. If you're neither, skip the checkout and just read the slides. The basic overview is that we memoize attribute generation by tracking the references involved, dismantling them and creating a memoized version that copies refs passed to the 'has' call into the right place in the code, so (excuse the ugliness of the generated output)

  has foo => (is => 'rw', required => 1, trigger => sub { $called_foo++ });

  ->

  BEGIN {
    %replay_has = (
  
      foo => sub {
        package Class::MOP::Method::Generated;
  use strict;
  use warnings;
        {
          my $__captures = {
    "\$attr_trigger" => \undef,
    "\$meta" => \"MooseX::Antlers::ErrorThrower"
  }
  ;
          # fixup code for external references
  $__captures->{"\$attr_trigger"} = \((\@_)->[6]);
            my $attr_trigger = ${$__captures->{'$attr_trigger'}};
    my $meta = ${$__captures->{'$meta'}};
  
          *One::foo = Sub::Name::subname "One::foo" => #line 39 "accessor foo defined at /home/matthewt/wdir/MooseX-Antlers/lib/MooseX/Antlers/Compiler.pm"
  sub { 
  
  if (scalar(@_) >= 2) {
  
  (@_ >= 2) || $meta->throw_error("Attribute (foo) is required, so cannot be set to undef");
  
  
  my @old = exists $_[0]->{"foo"} ? $_[0]->{"foo"} : ();
  
  $_[0]->{"foo"} = $_[1];
  $attr_trigger->($_[0], $_[1], @old);
   }
  
  
  return $_[0]->{"foo"}
   };
        }
      
      }
  
    );
  }

With a little more cleverness, we can do the same trick to be able to memoize immutable - memoize the constructor, and on-demand revivify the metaclass.

So - what doesn't this yet do?

- We hack around Moose closing over the metaclass for ->throw_error; we need to change that so that the metaclass' error_class can provide an inlined version of itself and adjust the Moose test suite to be happy about it.

- We currently only compile 'has' calls; this seemed like the hardest target to me since it forced the ref deconstruct/reconstruct stuff to be written but it means there's plenty still to do.

- Method modifiers are probably the next step - either change them to eval-based or factor out the generation code, possibly by switching them to use a (tweaked?) Class::Method::Modifiers.

- Role composition needs to be handled; with a bit of luck we can also memoize the effect of role composition as a routine of its own so that runtime role application can still sort-of use the cache.

- Devel::Declare based code needs to be capable of producing a memoized version of itself and we need to play with tracking the source code changes to produce a compiled form.

- The Sub::Name based stuff should go in favour of code declaring a named sub then yanking it back out the symbol table and filling in the lexicals later (this won't work for e.g. method keyword usage with lexicals captures but it'll get us quite far); that way code that doesn't require the Sub::Name approach doesn't need to have an XS dep forced onto it in compiled form.

If we get all of this cleared, I think compiling MooseX::Declare classes is pretty doable, let alone plain Moose ones. And we don't run into the key problem previous attempts have had, that building the metaclass from memoized is almost as slow as building it normally - because we don't bother building it at all unless somebody asks for it.

There's a fair amount of work to do yet, but given the ref re-assembly technique I think most of it's a Simple Matter of Programming. If you're interested, come wave on #moose-dev on irc.perl.org and I'll be happy to help you have a play.

-- mst, out.