Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Crafting Custom Interfaces with Sub Exporter

Crafting Custom Interfaces with Sub Exporter

Everybody knows about Exporter.pm: you use it, and if someone uses your module, they don't have to type quite as much. We'll look at how the Exporter works, and how it fails to take advantage of the powerful concepts on which it's built. We'll see how you can provide flexible import routines that allow your module's user to type even less and get code that behaves much more like part of his own program. You can avoid repeating unnecessary parameters to every overly-generic routine and can avoid collision-prone global configuration. All of this is made possible -- and easy -- by Sub::Exporter.

Subroutine generators -- routines that build routines -- can produce customized code, built to each importer's specifications. Sub::Exporter lets you build and provide customized routines easily. You'll learn how to write generators, and how to use them with Sub::Exporter . In its simplest form, it's as easy to use as Exporter.pm. With just a bit more configuration, it can build, group, rename, and julienne routines easily. With this tool, you'll be able to provide interfaces that are both simpler and more powerful than those provided by the stock Exporter.

Ricardo Signes

June 27, 2006
Tweet

More Decks by Ricardo Signes

Other Decks in Programming

Transcript

  1. What is Importing? •Build it over there, then bring it

    here. •For our purposes, “it” is code.
  2. Why Do We Import? •We want someone else to do

    the hard, boring work. •And we want it done cheap.
  3. sub strftime { my($pkg,$fmt,$time); ($pkg,$fmt,$time,$tzname) = @_; my $me =

    ref($pkg) ? $pkg : bless []; if(defined $tzname) { $tzname = uc $tzname; $tzname = sprintf(“%+05d”,$tzname) unless($tzname =~ /\D/); $epoch = timegm(@{$time}[0..5]); @$me = gmtime($epoch + tz_offset($tzname) - tz_offset()); } else { @$me = @$time; undef $epoch; } _subs($me,$fmt); }
  4. How Importing Works •the client use-s a module •the module’s

    import method is called •something ugly happens
  5. How Importing Works •the client use-s a module •the module’s

    import method is called •something ugly happens •the client has more named subs
  6. How Importing Works •usually that ugliness is Exporter.pm # the

    dark and twisted heart of Exporter.pm *{“${callpkg}::$sym”} = \&{“${pkg}::$sym”};
  7. The Factory Model •One size fits all •If it doesn’t

    fit your code, adjust your code. •Or abuse the Exporter
  8. The Tool Metaphor •“You can’t write good code without good

    tools.” •Exporters are tools for making tools.
  9. The Tool Metaphor •“You can’t write good code without good

    tools.” •Exporters are tools for making tools. •Their quality has an impact all the way down the line.
  10. Craftsman Tools •We want adaptable tools, customized for our current

    needs. •We want tools hand-crafted to our specifications.
  11. Craftsman Tools •We want adaptable tools, customized for our current

    needs. •We want tools hand-crafted to our specifications. •We want to reduce our labor by having someone else do the boring work.
  12. Basic Groups package String::Truncate; use Sub::Exporter -setup => { exports

    => [ qw(elide trunc) ], groups => { all => [qw(elide trunc)] }, };
  13. Basic Groups package String::Truncate; use Sub::Exporter -setup => { exports

    => [ qw(elide trunc) ], groups => { basic => [qw(elide trunc)] } };
  14. Basic Defaults package String::Truncate; use Sub::Exporter -setup => { exports

    => [ qw(elide trunc) ], groups => { basic => [ qw(elide trunc) ], default => [ qw(trunc) ] }, };
  15. package String::Truncate; use Exporter; use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);

    @ISA = qw(Exporter); @EXPORT = qw(trunc); @EXPORT_OK = qw(elide trunc); %EXPORT_TAGS = ( all => \@EXPORT_OK, basic => [ qw(elide trunc) ], ); Using Exporter.pm
  16. Renaming Exports use Exporter::Renaming; use String::Truncate qw(elide), Renaming => [

    trunc => ‘trunc_str’ ]; no Exporter::Renaming; Using Exporter::Renaming
  17. Renaming Exports use String::Truncate qw(trunc), trunc => { -as =>

    ‘trunc_str’ }; To let your client write:
  18. Renaming Exports package String::Truncate; use Sub::Exporter -setup => { exports

    => [ qw(elide trunc) ], groups => { basic => [ qw(elide trunc) ], default => [ qw(trunc) ] }, };
  19. Data::OptList @optlist = ( qw(alfa bravo), charlie => [ 0,

    1, 2 ], delta => { a => 1 }, ‘echo’, foxtrox => undef, ‘gulf’, );
  20. Data::OptList @optlist = ( qw(alfa bravo), charlie => [ 0,

    1, 2 ], delta => { a => 1 }, ‘echo’, foxtrox => undef, ‘gulf’, ); $as_href = { alfa => undef, bravo => undef, charlie => [ 0, 1, 2], delta => { a => 1 }, echo => undef, foxtrot => undef, gulf => undef, ];
  21. Data::OptList @optlist = ( qw(alfa bravo), charlie => [ 0,

    1, 2 ], delta => { a => 1 }, ‘echo’, foxtrox => undef, ‘gulf’, ); $as_aref = [ [ alfa => undef ], [ bravo => undef ], [ charlie => [0,1,2] ], [ delta => {a => 1}], [ echo => undef ], [ foxtrot => undef ], [ gulf => undef ], ];
  22. Data::OptList @optlist = ( qw(aye aye) love => [ qw(chex)

    ], love => [ qw(milk) ], aye => { sir => ‘!’ }, );
  23. Data::OptList @optlist = ( qw(aye aye) love => [ qw(chex)

    ], love => [ qw(milk) ], aye => { sir => ‘!’ }, ); $as_aref = [ [ aye => undef ], [ aye => undef ], [ love => [qw(chex)] ], [ love => [qw(milk)] ], [ aye => {sir => ‘!’}] ];
  24. Data::OptList $as_href = die “...”; @optlist = ( qw(aye aye)

    love => [ qw(chex) ], love => [ qw(milk) ], aye => { sir => ‘!’ }, );
  25. Subclassed Exporters • *{“$::$”} = \&{“$::$”}; • @EXPORT has to

    be defined in the derived class •the export has to be defined in the exporting package
  26. package String::Truncate::Split; use base qw(String::Truncate); our @EXPORT_OK = @String::Truncate::EXPORT_OK; our

    @EXPORT = @String::Truncate::EXPORT; our %EXPORT_TAGS = %String::Truncate::EXPORT_TAGS; sub trunc { my ($string, $length) = @_; # ... return ($head, $tail); }
  27. package String::Truncate::Split; use base qw(String::Truncate); our @EXPORT_OK = @String::Truncate::EXPORT_OK; our

    @EXPORT = @String::Truncate::EXPORT; our %EXPORT_TAGS = %String::Truncate::EXPORT_TAGS; sub trunc { my ($string, $length) = @_; # ... return ($head, $tail); } *$_ = \&{“String::Truncate::$_”} for grep { not defined &{__PACKAGE__.“::$_”} } @EXPORT;
  28. package String::Truncate::Split; use base qw(String::Truncate); our @EXPORT_OK = @String::Truncate::EXPORT_OK; our

    @EXPORT = @String::Truncate::EXPORT; our %EXPORT_TAGS = %String::Truncate::EXPORT_TAGS; sub trunc { my ($string, $length) = @_; # ... return ($head, $tail); } do { no strict ‘refs’; *$_ = \&{“String::Truncate::$_”} for grep { not defined &{__PACKAGE__.“::$_”} } @EXPORT; }
  29. Subclassed Exporters •Sub::Exporter finds exports with “can” •this means you

    can subclass exporting toolkits, replacing just pieces
  30. Package-Level Config use String::Truncate (); use Tools::Useful; sub trunc {

    my ($string, $length) = @_; $length //= 20; String::Truncate::trunc($string, $length) }
  31. Package-Level Config use String::Truncate (); use Tools::Useful; sub trunc {

    local $String::Truncate::DEFAULT_LENGTH = 20; String::Truncate::trunc(@_); }
  32. Custom Imports use String::Truncate qw(trunc), elide => { -as =>

    ‘trail_off’, marker => ‘etc’, };
  33. Custom Imports use String::Truncate qw(trunc elide), elide => { -as

    => ‘trail_off’, marker => ‘etc’, };
  34. Custom Imports use String::Truncate trunc => { -as => ‘trunc_str’,

    length => 10 }, elide => { -as => ‘elide_str’, length => 10 };
  35. Exports to Order package String::Truncate; use Sub::Exporter -setup => {

    exports => [ qw(elide trunc) ], groups => { basic => [ qw(elide trunc) ], default => [ qw(trunc) ] }, };
  36. Exports to Order package String::Truncate; use Sub::Exporter -setup => {

    exports => [ qw(elide trunc) ], groups => { basic => [ qw(elide trunc) ], default => [ qw(trunc) ] }, };
  37. Exports to Order package String::Truncate; use Sub::Exporter -setup => {

    exports => [ elide => undef, trunc => undef, ], groups => { basic => [ qw(elide trunc) ], default => [ qw(trunc) ] }, };
  38. Exports to Order package String::Truncate; use Sub::Exporter -setup => {

    exports => [ elide => \’_build_elide’, trunc => \’_build_trunc’, ], groups => { basic => [ qw(elide trunc) ], default => [ qw(trunc) ] }, };
  39. Generating Routines sub _build_trunc { my ($class, $name, $arg) =

    @_; my $_length = $arg->{length}; return sub {
  40. Generating Routines sub _build_trunc { my ($class, $name, $arg) =

    @_; my $_length = $arg->{length}; return sub { my ($string, $length, @rest) = @_;
  41. Generating Routines sub _build_trunc { my ($class, $name, $arg) =

    @_; my $_length = $arg->{length}; return sub { my ($string, $length, @rest) = @_; $length //= $_length;
  42. Generating Routines sub _build_trunc { my ($class, $name, $arg) =

    @_; my $_length = $arg->{length}; return sub { my ($string, $length, @rest) = @_; $length //= $_length; trunc($string, $length, @rest);
  43. Generating Routines sub _build_trunc { my ($class, $name, $arg) =

    @_; my $_length = $arg->{length}; return sub { my ($string, $length, @rest) = @_; $length //= $_length; trunc($string, $length, @rest); }
  44. Generating Routines sub _build_trunc { my ($class, $name, $arg) =

    @_; my $_length = $arg->{length}; return sub { my ($string, $length, @rest) = @_; $length //= $_length; trunc($string, $length, @rest); } }
  45. Routines ex nihilo use Cypher::Trivial qw(cyphers); my ($encyph, $decyph) =

    cyphers(“secret”); $cyphertext = $encyph->(“Top secret message.”);
  46. Routines ex nihilo use Cypher::Trivial qw(cyphers); my ($encyph, $decyph) =

    cyphers(“secret”); $cyphertext = $encyph->(“Top secret message.”); sub encypher {
  47. Routines ex nihilo use Cypher::Trivial qw(cyphers); my ($encyph, $decyph) =

    cyphers(“secret”); $cyphertext = $encyph->(“Top secret message.”); sub encypher { my $text = shift; $encyph->($text);
  48. Routines ex nihilo use Cypher::Trivial qw(cyphers); my ($encyph, $decyph) =

    cyphers(“secret”); $cyphertext = $encyph->(“Top secret message.”); sub encypher { my $text = shift; $encyph->($text); }
  49. Routines ex nihilo use Cypher::Trivial encypher => { secret =>

    “secret” }; encypher(“Top secret message”);
  50. Routines ex nihilo sub _build_encypher { my ($class, $name, $arg)

    = @_; my ($enc, $dec) = cyphers($arg->{secret}); return $enc; } sub _build_decypher { my ($class, $name, $arg) = @_; my ($enc, $dec) = cyphers($arg->{secret}); return $dec; }
  51. Routines ex nihilo sub _build_encypher { my ($class, $name, $arg)

    = @_; my ($enc, $dec) = cyphers($arg->{secret}); return $enc; } sub _build_decypher { my ($class, $name, $arg) = @_; my ($enc, $dec) = cyphers($arg->{secret}); return $dec; }
  52. Routines ex nihilo package Cypher::Trivial; use Sub::Exporter -setup => {

    exports => [ encypher => \’_build_encypher’, decypher => \’_build_decypher’, cyphers => undef, ], groups => { cyphers => [ qw(encypher decypher) ], } };
  53. Routines ex nihilo use Cypher::Trivial encypher => { secret =>

    “secret” }; encypher(“Top secret message”);
  54. Routines ex nihilo use Cypher::Trivial -cyphers => { secret =>

    “secret” }; encypher(“Top secret message”); decypher(“Gbc frperg zrffntr”);
  55. Generating Groups package Cypher::Trivial; use Sub::Exporter -setup => { exports

    => [ encypher => \’_build_encypher’, decypher => \’_build_decypher’, cyphers => undef, ], groups => { cyphers => [ qw(encypher decypher) ], } };
  56. Generating Groups sub _build_encypher { my ($class, $name, $arg) =

    @_; my ($enc, $dec) = cyphers($arg->{secret}); return $enc; } sub _build_decypher { my ($class, $name, $arg) = @_; my ($enc, $dec) = cyphers($arg->{secret}); return $dec; }
  57. Generating Groups package Cypher::Trivial; use Sub::Exporter -setup => { exports

    => [ encypher => \’_build_encypher’, decypher => \’_build_decypher’, cyphers => undef, ], groups => { cyphers => \’_build_cyphers’, } };
  58. Generating Groups sub _build_cyphers { my ($class, $name, $arg) =

    @_; my ($enc, $dec) = cyphers($arg->{secret});
  59. Generating Groups sub _build_cyphers { my ($class, $name, $arg) =

    @_; my ($enc, $dec) = cyphers($arg->{secret}); return {
  60. Generating Groups sub _build_cyphers { my ($class, $name, $arg) =

    @_; my ($enc, $dec) = cyphers($arg->{secret}); return { encypher => $enc,
  61. Generating Groups sub _build_cyphers { my ($class, $name, $arg) =

    @_; my ($enc, $dec) = cyphers($arg->{secret}); return { encypher => $enc, decypher => $dec,
  62. Generating Groups sub _build_cyphers { my ($class, $name, $arg) =

    @_; my ($enc, $dec) = cyphers($arg->{secret}); return { encypher => $enc, decypher => $dec, };
  63. Generating Groups sub _build_cyphers { my ($class, $name, $arg) =

    @_; my ($enc, $dec) = cyphers($arg->{secret}); return { encypher => $enc, decypher => $dec, }; }
  64. Generating Groups use Cypher::Trivial -cyphers => { secret => “secret”

    }, -cyphers => { secret => ‘Secret1234’, -suffix => ‘_strong’ } ;
  65. package Object::Hybrid; use Exporter; @Object::Exporter::ISA = qw(Exporter); @Object::Exporter::EXPORT_OK = qw(retrieve);

    sub retrieve { my ($class, $id) = @_; my $row = $class->get_row(id => $id); bless $row => $class; } Methods & Exporter.pm
  66. package Object::Hybrid; use Exporter; @Object::Exporter::ISA = qw(Exporter); @Object::Exporter::EXPORT_OK = qw(object);

    sub retrieve { my ($class, $id) = @_; my $row = $class->get_row(id => $id); bless $row => $class; } sub object { __PACKAGE__->retrieve(@_) } Methods & Exporter.pm
  67. use Object::Hybrid; use Object::Hybrid::With::Much::Derivation; my $object = Object::Hybrid->retrieve(42); my $thing

    = Object::Hybrid::With::Much::Derivation ->retrieve(49); Methods & Exporter.pm
  68. package Object::Hybrid; use Exporter; @Object::Exporter::ISA = qw(Exporter); @Object::Exporter::EXPORT_OK = qw(object);

    sub retrieve { my ($class, $id) = @_; my $row = $class->get_row(id => $id); bless $row => $class; } sub object { __PACKAGE__->retrieve(@_) } Methods & Exporter.pm
  69. Currying Methods use Object::Hybrid qw(object); use Object::Hybrid::With::Much::Derivation object => {

    -as => ‘derived_object’ }; my $object = object(42); my $thing = derived_object(49);
  70. Currying Methods use Sub::Exporter -setup => { exports => [

    object => \&_build_object ], }; sub _build_object {
  71. Currying Methods use Sub::Exporter -setup => { exports => [

    object => \&_build_object ], }; sub _build_object { my ($class, $name, $arg) = @_;
  72. Currying Methods use Sub::Exporter -setup => { exports => [

    object => \&_build_object ], }; sub _build_object { my ($class, $name, $arg) = @_; return sub { $class->new(@_); }
  73. Currying Methods use Sub::Exporter -setup => { exports => [

    object => \&_build_object ], }; sub _build_object { my ($class, $name, $arg) = @_; return sub { $class->new(@_); } }
  74. Currying Methods use Sub::Exporter -setup => { exports => [

    object => curry_class(‘new’) ], }
  75. Exporting Methods •Sometimes you want to export methods without currying

    the class. •Exporters can serve as method crafters.
  76. Exporting Methods package Mixin::Dumper; use Sub::Exporter -setup => { exports

    => [ qw(dump) ], groups => { default => [ qw(dump) ] }, }; sub dump { my ($self) = @_; require Data::Dumper; Data::Dumper::Dumper($self); }
  77. Exporting Methods package Email::Simple::mixin:ReplyText; use Sub::Exporter -setup => { exports

    => [ qw(reply_text) ], groups => { defaults => [ qw(reply_text) ] }, }; sub reply_text { my ($self) = @_; join “\n”, map “>$_”, split /\n/, $self->body; }
  78. Exporting Methods package Email::Simple::mixin:ReplyText; use Sub::Exporter -setup => { into

    => ‘Email::Simple’, exports => [ qw(reply_text) ], groups => { defaults => [ qw(reply_text) ] }, }; sub reply_text { my ($self) = @_; join “\n”, map “>$_”, split /\n/, $self->body; }
  79. Emulating mixin.pm •This makes it easy to import a chunk

    of methods and override just a few...
  80. Emulating mixin.pm •This makes it easy to import a chunk

    of methods and override just a few... •...and those few can call SUPER.
  81. Emulating mixin.pm package Email::Simple::mixin:ReplyText; use Sub::Exporter -setup => { into

    => ‘Email::Simple’, exports => [ qw(reply_text) ], groups => { defaults => [ qw(reply_text) ] }, }; sub reply_text { my ($self) = @_; join “\n”, map “>$_”, split /\n/, $self->body; }
  82. Emulating mixin.pm package Email::Simple::mixin:ReplyText; use Sub::Exporter -setup => { into

    => ‘Email::Simple’, exporter=> mixin_exporter, exports => [ qw(reply_text) ], groups => { defaults => [ qw(reply_text) ] }, }; sub reply_text { my ($self) = @_; join “\n”, map “>$_”, split /\n/, $self->body; }
  83. Collectors package String::Truncate; use Sub::Exporter -setup => { exports =>

    [ elide => \’_build_elide’, trunc => \’_build_trunc’, ], collectors => [ qw(defaults) ], };
  84. Collectors use String::Truncate defaults => { length => 10 },

    qw(-all), trunc => { length => 1, -as => ‘onechar’ },
  85. Collectors use String::Truncate defaults => { length => 10 },

    qw(-all), trunc => { length => 1, -as => ‘onechar’ }, elide => { marker => ‘&c’, -as => ‘yul’ },
  86. Collectors use String::Truncate defaults => { length => 10 },

    qw(-all), trunc => { length => 1, -as => ‘onechar’ }, elide => { marker => ‘&c’, -as => ‘yul’ }, ;
  87. Collectors sub _build_trunc { my ($class, $name, $arg) = @_;

    my $_length = $arg->{length}; return sub { my ($string, $length, @rest) = @_; $length = $_length if !defined $length; trunc($string, $length, @rest); } }
  88. Collectors sub _build_trunc { my ($class, $name, $arg, $col) =

    @_; my $_length = $arg->{length}; return sub { my ($string, $length, @rest) = @_; $length = $_length if !defined $length; trunc($string, $length, @rest); } }
  89. Collectors sub _build_trunc { my ($class, $name, $arg, $col) =

    @_; my $_length = $arg->{length}; $_length = $col->{defaults}{length} if !defined $_length; return sub { my ($string, $length, @rest) = @_; $length = $_length if !defined $length; trunc($string, $length, @rest); } }
  90. Collectors package String::Truncate; use Sub::Exporter -setup => { exports =>

    [ elide => \’_build_elide’, trunc => \’_build_trunc’, ], collectors => { defaults => \’_validate_defaults’, }, };
  91. Collectors •Arguments that don’t export. •They collect data for generators

    to use. •They can validate the collected data. •They can do Almost Anything Else.
  92. Collectors •name - name of the collection •class - invocant

    of import method •config - exporter configuration
  93. Collectors •name - name of the collection •class - invocant

    of import method •config - exporter configuration •into - the package that’s importing
  94. Collectors •name - name of the collection •class - invocant

    of import method •config - exporter configuration •into - the package that’s importing •import_args - args to import method
  95. Collectors •config - the Sub::Exporter config •find out what exports

    exist •validate collection value based on config
  96. use LWP::Simple like => [ qr/^is_/, undef, qr/^get/, { -prefix

    => ‘https_’, ssl => 1 } ]; is_success($res); is_failure($res); https_get(“https://codesimply.com”)
  97. Collectors •into - the target to which exports go •alter

    the class directly •particularly useful: @ISA
  98. sub _make_base { my ($class, $value, $data) = @_; my

    $target = $data->{into}; push @{“$target\::ISA”}, $class; }
  99. sub _make_base { my ($class, $value, $data) = @_; my

    $target = $data->{into}; push @{“$target\::ISA”}, $class; } use Sub::Exporter -setup => { collectors => { -base => \’_make_base’ }, };
  100. sub _make_base { my ($class, $value, $data) = @_; my

    $target = $data->{into}; push @{“$target\::ISA”}, $class; } use Sub::Exporter -setup => { collectors => { -base => \’_make_base’ }, }; use Magic::Superclass -base;
  101. package Email::Constants; sub _set_constants { my ($class, $value, $data) =

    @_; Package::Generator->assign_symbols( $data->{into}, [ EX_TEMPFAIL => 75, FORMATS => [ qw(Maildir mbox mh) ], ], ); }
  102. package Email::Constants; sub _set_constants { my ($class, $value, $data) =

    @_; Package::Generator->assign_symbols( $data->{into}, [ EX_TEMPFAIL => 75, FORMATS => [ qw(Maildir mbox mh) ], ], ); } use Sub::Exporter -setup => { collectors => { constants => \’_set_constants’ }, };
  103. sub _setup { my ($class, $value, $data) = @_; if

    (ref $value eq ‘HASH’) { push @{ $data->{import_args} },
  104. sub _setup { my ($class, $value, $data) = @_; if

    (ref $value eq ‘HASH’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, %$value } ];
  105. sub _setup { my ($class, $value, $data) = @_; if

    (ref $value eq ‘HASH’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, %$value } ]; return 1;
  106. sub _setup { my ($class, $value, $data) = @_; if

    (ref $value eq ‘HASH’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, %$value } ]; return 1; } elsif (ref $value eq ‘ARRAY’) {
  107. sub _setup { my ($class, $value, $data) = @_; if

    (ref $value eq ‘HASH’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, %$value } ]; return 1; } elsif (ref $value eq ‘ARRAY’) { push @{ $data->{import_args} },
  108. sub _setup { my ($class, $value, $data) = @_; if

    (ref $value eq ‘HASH’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, %$value } ]; return 1; } elsif (ref $value eq ‘ARRAY’) { push @{ $data->{import_args} }, [ _import => {
  109. sub _setup { my ($class, $value, $data) = @_; if

    (ref $value eq ‘HASH’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, %$value } ]; return 1; } elsif (ref $value eq ‘ARRAY’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, exports => $value } ];
  110. sub _setup { my ($class, $value, $data) = @_; if

    (ref $value eq ‘HASH’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, %$value } ]; return 1; } elsif (ref $value eq ‘ARRAY’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, exports => $value } ]; return 1;
  111. sub _setup { my ($class, $value, $data) = @_; if

    (ref $value eq ‘HASH’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, %$value } ]; return 1; } elsif (ref $value eq ‘ARRAY’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, exports => $value } ]; return 1; }
  112. sub _setup { my ($class, $value, $data) = @_; if

    (ref $value eq ‘HASH’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, %$value } ]; return 1; } elsif (ref $value eq ‘ARRAY’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, exports => $value } ]; return 1; } return;
  113. sub _setup { my ($class, $value, $data) = @_; if

    (ref $value eq ‘HASH’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, %$value } ]; return 1; } elsif (ref $value eq ‘ARRAY’) { push @{ $data->{import_args} }, [ _import => { -as => ‘import’, exports => $value } ]; return 1; } return; }
  114. use Sub::Exporter -setup => { collectors => { -setup =>

    \’_setup’ }, exports => [ _import => \’_build_import’ ],
  115. use Sub::Exporter -setup => { collectors => { -setup =>

    \’_setup’ }, exports => [ _import => \’_build_import’ ], });
  116. -setup => { into_level => 2, exports => [qw(foo)] }

    _import => { -as => ‘import’, into_level => 2, exports => [qw(foo)] }
  117. -setup => [ qw(foo bar baz) ] _import => {

    -as => ‘import’, exports => [qw(foo bar baz)] }
  118. use Sub::Exporter -setup => { collectors => { -setup =>

    \’_setup’ }, exports => [ _import => \’_build_import’ ], });
  119. use Sub::Exporter -setup => { collectors => { -setup =>

    \’_setup’ }, exports => [ _import => sub { my ($class, $name, $arg) = @_; build_exporter($arg); }, ], });
  120. package Sub::Exporter; use Sub::Exporter -setup => { collectors => {

    -setup => \&_setup }, exports => [ _import => sub { my ($class, $name, $arg) = @_; build_exporter($arg); }, ], });
  121. RJBS’s Advice •Write the client code first. •Make as many

    assumptions as possible. •Let most of them be refuted.
  122. Mixed-in Helpers sub _build_cplx_method { my ($mixin) = @_; sub

    { my ($self, $arg) = @_; $mixin->validate_arg($arg); $mixin->do_stuff($self, $arg); return $mixin->analyze($self); } } sub validate_arg {...}
  123. Mixed-in Helpers package Mixin::Helper; use Sub::Exporter -setup => { exports

    => [ complex_method => \’_build_cplx_method’, ], }; sub _build_cplx_method { ...
  124. Mixed-in Helpers sub _build_cplx_method { my ($mixin) = @_; sub

    { my ($self, $arg) = @_; $mixin->validate_arg($arg); $mixin->do_stuff($self, $arg); return $mixin->analyze($self); } } sub validate_arg {...}
  125. package YAPC::Slideshow; use Accessors::Simple -setup => { fields => [

    qw(topic presenter timeslot room) ], }; Accessors sans ISA
  126. sub _make_accessor { my ($field) = @_; sub { my

    ($self) = shift; Accessors sans ISA
  127. sub _make_accessor { my ($field) = @_; sub { my

    ($self) = shift; $self->{field} = shift if @_; Accessors sans ISA
  128. sub _make_accessor { my ($field) = @_; sub { my

    ($self) = shift; $self->{field} = shift if @_; return $self->{$field}; Accessors sans ISA
  129. sub _make_accessor { my ($field) = @_; sub { my

    ($self) = shift; $self->{field} = shift if @_; return $self->{$field}; } Accessors sans ISA
  130. sub _make_accessor { my ($field) = @_; sub { my

    ($self) = shift; $self->{field} = shift if @_; return $self->{$field}; } } Accessors sans ISA
  131. sub _make_accessor { my ($field) = @_; sub { my

    ($self) = shift; $self->{field} = shift if @_; return $self->{$field}; } } sub _make_many_accessors { Accessors sans ISA
  132. sub _make_accessor { my ($field) = @_; sub { my

    ($self) = shift; $self->{field} = shift if @_; return $self->{$field}; } } sub _make_many_accessors { my @fields = @{ $arg->{fields} }; Accessors sans ISA
  133. sub _make_accessor { my ($field) = @_; sub { my

    ($self) = shift; $self->{field} = shift if @_; return $self->{$field}; } } sub _make_many_accessors { my @fields = @{ $arg->{fields} }; my %sub = map { $_ => _make_accessor($_) } @fields; Accessors sans ISA
  134. sub _make_accessor { my ($field) = @_; sub { my

    ($self) = shift; $self->{field} = shift if @_; return $self->{$field}; } } sub _make_many_accessors { my @fields = @{ $arg->{fields} }; my %sub = map { $_ => _make_accessor($_) } @fields; return \%sub; Accessors sans ISA
  135. sub _make_accessor { my ($field) = @_; sub { my

    ($self) = shift; $self->{field} = shift if @_; return $self->{$field}; } } sub _make_many_accessors { my @fields = @{ $arg->{fields} }; my %sub = map { $_ => _make_accessor($_) } @fields; return \%sub; } Accessors sans ISA
  136. sub _make_accessor { my ($field) = @_; sub { my

    ($self) = shift; $self->{field} = shift if @_; return $self->{$field}; } } sub _make_many_accessors { my @fields = @{ $arg->{fields} }; my %sub = map { $_ => _make_accessor($_) } @fields; return \%sub; } use Sub::Exporter -setup => Accessors sans ISA
  137. sub _make_accessor { my ($field) = @_; sub { my

    ($self) = shift; $self->{field} = shift if @_; return $self->{$field}; } } sub _make_many_accessors { my @fields = @{ $arg->{fields} }; my %sub = map { $_ => _make_accessor($_) } @fields; return \%sub; } use Sub::Exporter -setup => { groups => { setup => \&_make_many_accessors } }; Accessors sans ISA
  138. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Eat Exporter’s Brain
  139. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Sub::Exporter::setup_exporter({ Eat Exporter’s Brain
  140. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Sub::Exporter::setup_exporter({ as => ‘import’, Eat Exporter’s Brain
  141. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, Eat Exporter’s Brain
  142. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], Eat Exporter’s Brain
  143. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { Eat Exporter’s Brain
  144. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, Eat Exporter’s Brain
  145. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, default => [ @{“$pkg\::EXPORTS”} ], Eat Exporter’s Brain
  146. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, default => [ @{“$pkg\::EXPORTS”} ], }, Eat Exporter’s Brain
  147. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, default => [ @{“$pkg\::EXPORTS”} ], }, }); Eat Exporter’s Brain
  148. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, default => [ @{“$pkg\::EXPORTS”} ], }, }); push @{“$new_pkg\::ISA”}, $class; Eat Exporter’s Brain
  149. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, default => [ @{“$pkg\::EXPORTS”} ], }, }); push @{“$new_pkg\::ISA”}, $class; return $new_pkg; Eat Exporter’s Brain
  150. sub exporter_upgrade { my ($pkg) = @_; my $new_pkg =

    “$pkg\::SE”; Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, default => [ @{“$pkg\::EXPORTS”} ], }, }); push @{“$new_pkg\::ISA”}, $class; return $new_pkg; } Eat Exporter’s Brain
  151. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg);
  152. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg); Sub::Exporter::setup_exporter({
  153. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg); Sub::Exporter::setup_exporter({ as => ‘import’,
  154. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg); Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg,
  155. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg); Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ],
  156. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg); Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => {
  157. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg); Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”},
  158. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg); Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, default => [ @{“$pkg\::EXPORTS”} ],
  159. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg); Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, default => [ @{“$pkg\::EXPORTS”} ], },
  160. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg); Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, default => [ @{“$pkg\::EXPORTS”} ], }, });
  161. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg); Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, default => [ @{“$pkg\::EXPORTS”} ], }, }); push @{“$new_pkg\::ISA”}, $class;
  162. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg); Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, default => [ @{“$pkg\::EXPORTS”} ], }, }); push @{“$new_pkg\::ISA”}, $class; return $new_pkg;
  163. package UNIVERSAL; sub exporter_upgrade { my ($pkg) = @_; my

    $new_pkg = “$pkg\::SE”; return $new_pkg if $new_pkg->isa($pkg); Sub::Exporter::setup_exporter({ as => ‘import’, into => $new_pkg, exports => [ @{“$pkg\::EXPORT_OK”} ], groups => { %{“$pkg\::EXPORT_TAGS”}, default => [ @{“$pkg\::EXPORTS”} ], }, }); push @{“$new_pkg\::ISA”}, $class; return $new_pkg; }
  164. Fixing caller sub default_exporter { my ($class, $gen, $name, $arg,

    $col, $as, $into) = @_; _install( _generate($class, $generator, $name, $arg, $col),
  165. Fixing caller sub default_exporter { my ($class, $gen, $name, $arg,

    $col, $as, $into) = @_; _install( _generate($class, $generator, $name, $arg, $col), $into,
  166. Fixing caller sub default_exporter { my ($class, $gen, $name, $arg,

    $col, $as, $into) = @_; _install( _generate($class, $generator, $name, $arg, $col), $into, $as,
  167. Fixing caller sub default_exporter { my ($class, $gen, $name, $arg,

    $col, $as, $into) = @_; _install( _generate($class, $generator, $name, $arg, $col), $into, $as, );
  168. Fixing caller sub default_exporter { my ($class, $gen, $name, $arg,

    $col, $as, $into) = @_; _install( _generate($class, $generator, $name, $arg, $col), $into, $as, ); }
  169. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do {
  170. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do { my $g = Dump(\&_generate)->Names(‘GEN’)->Out;
  171. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do { my $g = Dump(\&_generate)->Names(‘GEN’)->Out; $g =~ s/\A\$GEN = sub/sub _generate/;
  172. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do { my $g = Dump(\&_generate)->Names(‘GEN’)->Out; $g =~ s/\A\$GEN = sub/sub _generate/; $g = “package $into;\n$g”;
  173. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do { my $g = Dump(\&_generate)->Names(‘GEN’)->Out; $g =~ s/\A\$GEN = sub/sub _generate/; $g = “package $into;\n$g”; eval $g;
  174. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do { my $g = Dump(\&_generate)->Names(‘GEN’)->Out; $g =~ s/\A\$GEN = sub/sub _generate/; $g = “package $into;\n$g”; eval $g; \&{“$into\::_generate”};
  175. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do { my $g = Dump(\&_generate)->Names(‘GEN’)->Out; $g =~ s/\A\$GEN = sub/sub _generate/; $g = “package $into;\n$g”; eval $g; \&{“$into\::_generate”}; };
  176. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do { my $g = Dump(\&_generate)->Names(‘GEN’)->Out; $g =~ s/\A\$GEN = sub/sub _generate/; $g = “package $into;\n$g”; eval $g; \&{“$into\::_generate”}; }; _install(
  177. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do { my $g = Dump(\&_generate)->Names(‘GEN’)->Out; $g =~ s/\A\$GEN = sub/sub _generate/; $g = “package $into;\n$g”; eval $g; \&{“$into\::_generate”}; }; _install( $col->{_g}($class, $generator, $name, $arg, $col),
  178. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do { my $g = Dump(\&_generate)->Names(‘GEN’)->Out; $g =~ s/\A\$GEN = sub/sub _generate/; $g = “package $into;\n$g”; eval $g; \&{“$into\::_generate”}; }; _install( $col->{_g}($class, $generator, $name, $arg, $col), $into,
  179. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do { my $g = Dump(\&_generate)->Names(‘GEN’)->Out; $g =~ s/\A\$GEN = sub/sub _generate/; $g = “package $into;\n$g”; eval $g; \&{“$into\::_generate”}; }; _install( $col->{_g}($class, $generator, $name, $arg, $col), $into, $as,
  180. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do { my $g = Dump(\&_generate)->Names(‘GEN’)->Out; $g =~ s/\A\$GEN = sub/sub _generate/; $g = “package $into;\n$g”; eval $g; \&{“$into\::_generate”}; }; _install( $col->{_g}($class, $generator, $name, $arg, $col), $into, $as, );
  181. sub evil_eval_exporter { # TOTALLY UNTESTED! my ($class, $gen, $name,

    $arg, $col, $as, $into) = @_; $col->{_g} ||= do { my $g = Dump(\&_generate)->Names(‘GEN’)->Out; $g =~ s/\A\$GEN = sub/sub _generate/; $g = “package $into;\n$g”; eval $g; \&{“$into\::_generate”}; }; _install( $col->{_g}($class, $generator, $name, $arg, $col), $into, $as, ); }