About
Random notes: A pile of assorted scribblings, snippets and ramblings (mostly about programming and the software that makes my life easier).

Rhesa Rozendaal
Subscribe
Subscribe to a syndicated feed of my weblog.
Categories
Archive
February
Sun Mon Tue Wed Thu Fri Sat
     
8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29      
Links

2010. 11. 29

Devel::Declare, Method::Signatures, oh my


Actually, I got another inspiration from Method::Signatures, for use with CGI::Application.

But while implementing that, I decided to extract the hard Devel::Declare bits, and make a macro installer.

Here’s a re-implementation of Method::Signatures using my Devel::Declare::Macro:

 package Method::Signatures;
 use Devel::Declare::Macro;

 sub import {
  install_macro(
    into           => scalar(caller),
    name           => 'method',
    proto_parser   => \&make_proto_unwrap,
    proto_injector => \&inject_from_signature
  );
 }

 sub make_proto_unwrap {} # as before, except return \%signature
 sub inject_from_signature {} # as before
 1;

I’m somewhat confident it could simplify the implementation of Sub::Curried and MooseX::Method::Signatures as well.

Basically it packages the synopsis of Devel::Declare. As such, I don’t think I should maintain it. Comments welcome.

  package Devel::Macro;

  sub import {
      my $class = shift;
      my $pkg = caller;
      no strict 'refs';
      *{$pkg.'::install_macro'} = \&install_macro;
  }

  # Stolen from Devel::Declare's t/method-no-semi.t
  use Devel::Declare ();
  use Scope::Guard;
  use Sub::Name;
  sub install_macro {
      my %args   = @_;
      # I don't really understand why we need to declare method
      # in the caller's namespace.
      {
          no strict 'refs';
          *{$args{into}.'::'.$args{name}}   = sub (&) {};
      }
      Devel::Declare->setup_for(
          $args{into},
          { $args{name} => {
              const => mk_parser(
                  $args{proto_parser}||sub{''},
                  $args{proto_injector}||sub{join' ', @_},
                  $args{pre_install}||sub{},
                  )
              },
          },
      );

  }
  our ($Declarator, $Offset);

  sub skip_declarator {
      $Offset += Devel::Declare::toke_move_past_token($Offset);
  }

  sub skipspace {
      $Offset += Devel::Declare::toke_skipspace($Offset);
  }

  sub strip_name {
      skipspace;
      if (my $len = Devel::Declare::toke_scan_word($Offset, 1)) {
          my $linestr = Devel::Declare::get_linestr();
          my $name = substr($linestr, $Offset, $len);
          substr($linestr, $Offset, $len) = '';
          Devel::Declare::set_linestr($linestr);
          return $name;
      }
      return;
  }

  sub strip_proto {
      skipspace;

      my $linestr = Devel::Declare::get_linestr();
      if (substr($linestr, $Offset, 1) eq '(') {
          my $length = Devel::Declare::toke_scan_str($Offset);
          my $proto = Devel::Declare::get_lex_stuff();
          Devel::Declare::clear_lex_stuff();
          $linestr = Devel::Declare::get_linestr();
          substr($linestr, $Offset, $length) = '';
          Devel::Declare::set_linestr($linestr);
          return $proto;
      }
      return;
  }

  sub shadow {
      my $pack = Devel::Declare::get_curstash_name;
      Devel::Declare::shadow_sub("${pack}::${Declarator}", $_[0]);
  }

  sub inject_if_block {
      my $inject = shift;
      skipspace;
      my $linestr = Devel::Declare::get_linestr;
      if (substr($linestr, $Offset, 1) eq '{') {
          substr($linestr, $Offset+1, 0) = $inject;
          Devel::Declare::set_linestr($linestr);
      }
  }

  sub scope_injector_call {
      return ' BEGIN { ' . __PACKAGE__ . '::inject_scope }; ';
  }

  sub mk_parser {
      my $proto_parser   = shift;
      my $proto_injector = shift;
      my $install_cb     = shift;

      return sub {
      local ($Declarator, $Offset) = @_;
      skip_declarator;
      my $name = strip_name;
      my $proto = strip_proto;
      my @decl = $proto_parser->($proto);
      my $inject = $proto_injector->(@decl);
      if (defined $name) {
          $inject = scope_injector_call().$inject;
      }
      inject_if_block($inject);
      if (defined $name) {
          $name = join('::', Devel::Declare::get_curstash_name(), $name)
            unless ($name =~ /::/);
      }
      shadow(sub (&) {
          no strict 'refs';
          my $code = shift;
          $install_cb->($name, $code, \@decl);
          # So caller() gets the subroutine name
          *{$name} = subname $name => $code if defined $name;
      });
      };
  }

  sub inject_scope {
      $^H |= 0x120000;
      $^H{DD_METHODHANDLERS} = Scope::Guard->new(sub {
          my $linestr = Devel::Declare::get_linestr;
          my $offset = Devel::Declare::get_linestr_offset;
          substr($linestr, $offset, 0) = ';';
          Devel::Declare::set_linestr($linestr);
      });
  }

  1;


Test Driven Development and refactoring


On Safari Bookshelf:

  • “Test-Driven Development By Example”, Kent Beck
  • “Perl Testing: A Developer’s Notebook”, chromatic , Ian Langworth
  • “Refactoring: Improving the Design of Existing Code”, Martin Fowler, Kent Beck et al

On PerlMonks:

Other books I’ve heard good things about:

  • “Code Complete”, Second Edition, Steve McConnell
  • “The Pragmatic Programmer: From Journeyman to Master”, Andrew Hunt, David Thomas

Some references on OOP, Design patterns and Best Practices


General reading

On Safari Bookshelf:

  • “Advanced Perl Programming, 2nd Edition”, Simon Cozens
  • “Perl Best Practices”, Damian Conway

Elsewhere:

Advanced stuff