The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

MOP4Import::Intro - Thin Meta-Object Protocol to build extensible exporters

SYNOPSIS

Create inner-classes MyApp::Artist and MyApp::CD using MOP4Import::Types.

  package MyApp;
  use MOP4Import::Types
    (Artist => [[fields => qw/artistid name/]]
     , CD   => [[fields => qw/cdid artistid title year/]]);

  sub print_artist_cds {
    (my $self, my Artist $artist) = @_;
    my @cds = $self->DB->select(CD => {artistid => $artist->{artistid}});
    foreach my CD $cd (@cds) {
      print tsv($cd->{title}, $cd->{year}), "\n";
    }
  }

Define your exporter MyExporter with help of MOP4Import::Declare.

  package MyExporter {
    use MOP4Import::Declare -as_base;
    use MOP4Import::Util qw/globref/;
    
    sub declare_foo {
      my ($myPack, $opts, $callpack) = @_;
      # then add some method
      *{globref($callpack, 'foo')} = sub (@) { join("! ", "FOOOOOO", @_) };
    }
    
    sub declare_bar {
      my ($myPack, $opts, $callpack, $x, $y, @z) = @_;
      my $glob = globref($callpack, 'bar');
      *$glob = \ $x;
      *$glob = +{bar => $y};
      *$glob = \@z;
    }
  
    sub declare_say {
      my ($myPack, $opts, $callpack) = @_;
      require feature;
      feature->import('say');
    }
  };

Then use MyExporter in your class like following:

  ### Import MyExporter into your class MyApp.
  package MyApp;
  use MyExporter -say, -foo, [bar => "A", "x", 1..3];
  
  # Above means you called:
  #  use strict;
  #  use warnings;
  #  MyExporter->declare_say($opts, 'MyApp');
  #  MyExporter->declare_foo($opts, 'MyApp');
  #  MyExporter->declare_bar($opts, 'MyApp', "A", "x", 1..3);
  
  say foo bar => 3;
  
  say "scalar=$bar\t", "hash=$bar{bar}\t", "array=@bar";

DESCRIPTION

MOP4Import, "Meta-Object Protocol for Import", I propose here, is a set of (experimental) protocols (and their dispatcher implementations) to write exporter modules in some extensible ways.

For more about design background, see whyfields.

What kind of problems does this try to solve?

It is well known for Perl programmers that Perl's use behaves like following code:

  use YourExporter LIST;

  # means:

  BEGIN { require YourExporter; YourExporter->import( LIST ); }

And this YourExporter->import usually used to modify caller module just compiling about, like Exporter. This is a kind of Meta Programming. If correctly used, this can help reducing some boilerplates and have more chances to check programs statically.

However, since import is a method and a class can have atmost one import method, there is a difficulty about reusability. For example, imagine some module X can be used like following and you favored it:

  use X ':foo', ':bar';

You may want to reuse ":foo" pragma feature in your own module Y, but X::import is monolithically writen with "if .. elsif ...", so you end up to copy-n-paste specific portion to your Y::import. (And someday other guy find your :foo is nice... repeated.)

Source of this problem here is that single function import provides both protocol(entry point) and implementation, and there is no way to selectively reuse/override specific case of import specs.

So, I propose some protocols for this area. These protocols maps import list of "use Module LIST" into specific method invocations like Module->declare_foo() , Module->dispatch_pragma_bar()...

MODULES

MOP4Import::Declare

MOP4Import::Declare protocol dispatcher basically maps each import() arguments to method calls starting with declare_.... Typicall use of MOP4Import::Declare is like following:

  use MOP4Import::Declare -strict, [fields => qw/foo bar/];

Above means basically:

  BEGIN {
    require MOP4Import::Declare;
    
    MOP4Import::Declare->declare_strict(+{}, __PACKAGE__);
    
    MOP4Import::Declare->declare_fields(+{}, __PACKAGE__, qw/foo bar/);
  }

These methods are called MOP4Import declaration pragma, or simply pragma. This module also provides some useful pragmas like -strict, [fields => qw/foo bar/] .. shown above and serves as a base for other MOP4Import dispatchers.

If you are curious about my advocates of using fields, see whyfields.

This module also mimics type sigil specific import found in Exporter, so you don't have to write codes to export usual vars/subs/symbols.

MOP4Import::Types

MOP4Import::Types is another style of protocol dispatcher. This helps defining many inner classes at once like following:

  package MyApp;
  use MOP4Import::Types
    T1 => [pragma, pragma..],
    T2 => [pragma,...];

Above means basically:

  package MyApp;
  sub T1 () {'MyApp::T1'}
  package MyApp::T1 {
    use MOP4Import::Declare pragma, pragma..;
  }
  sub T2 () {'MyApp::T2'}
  package MyApp::T2 {
    use MOP4Import::Declare pragma, ...;
  }

From protocol perspective, first is briefly same as:

  package MyApp;
  BEGIN {
    require MOP4Import::Types;

    MOP4Import::Types->dispatch_pairs_as(type => +{}, $callpack
       T1 => [pragma, pragma..],
       T2 => [pragma,...]
    );
  }

And that is

  package MyApp;
  BEGIN {
    require MOP4Import::Types;

    MOP4Import::Types->declare___type(+{}, $callpack, T1 => pragma, pragma..);
    MOP4Import::Types->declare___type(+{}, $callpack, T2 => pragma,...);
  }

MOP4Import::Util

MOP4Import::Util provides several functions to handle GLOBs and %FIELDS such as globref($class,$name), fields_hash($class) ...

SAMPLES

MOP4Import::Base::Configure

MOP4Import::Base::Configure is a sample (but enough usable) base class for Object Orientation. To use this as a base class, simply:

  use MOP4Import::Base::Configure -as_base;

This class is designed to fully utilize statically checked fields. Also, fields getters are automatically generated for public fields. To set fields values, call $obj->configure(key => value, key2 => value2...). You can have set-hook as onconfigure_key($value).

Also there is its variant MOP4Import::Base::CLI, which is designed for Command Line applications.

MOP4Import::PSGIEnv

MOP4Import::PSGIEnv defines an inner-class Env with predeclared PSGI standard $env fields.

To use static checking for PSGI env, just use this module and change argument declaration from my ($env) to (my Env $env) .

Note: You are not limited to standard PSGI items. you can add fields just by listing your own extensions.

  use MOP4Import::PSGIEnv qw/mypsgi.extension/;
  
  return sub {
    (my Env $env) = @_;
    return [200, [], ["PATH_INFO is ", $env->{PATH_INFO}
                    , extension => $env->{'mypsgi.extension'}
                  ]];
  }

AUTHOR

KOBAYASHI, Hiroaki <hkoba@cpan.org>

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.