On Module::New

昨日のSoozy Con4で発表したスライド。Text::Hatenaで加工していたのでここに(ほとんど)そのまま貼り付けておきます。

2008/01/31追記:
最新版はこちらから。近日中にアップの予定はありますが、まだCPANにはあがっていません。
http://svn.coderepos.org/share/lang/perl/Module-New/trunk

Soozy Conf #4
Jan. 27, 2008
Kenichi Ishigaki (charsbar)

はじめに

  • 今日は日本語オンリー
  • Win32::IE::SlideShowとCharsbotというIRCボットでプレゼン動かします

Module::Newとは?

こんなの。

11:44 <t********> o
11:45 <t********> なんか妙なのが。
11:45 <t********> pmsetup みたいなやつっぽい
11:45 <m*******> 巨大だなぁw

pmsetupというより、Module::Starterを拡張したもの。

Module::Newの使い方

  > module_new dist Dist-Name
  > cd Dist-Name/trunk
  > module_new file Dist::Name::Plugin
  > module_new file t/dist_test.t --edit --type=Test
  > module_new manifest --force

実際には module_new というコマンドを new という名前にリネームして使っている。

Module::Newを使うと

  • モジュールのひな形をつくれる
  • テストやスクリプトのひな形もつくれる

この辺はあたりまえ。

  • モジュールをあとから追加できる
  • 下の方のディレクトリにいても大丈夫
  • エディタを起動してくれる
  • MANIFESTもアップデートしてくれる
  • テストを実行できたりもする(まだ公開してない)

大事なのはアフターサービス。

Module::Newの実装(1)

コマンドラインから与えた引数に対して、こんな感じのレシピを実行している。

package Module::New::Recipe::Dist;

use strict;
use warnings;
use Module::New::Command;

available_options qw( make=s shipit test|t=s@ edit|e );

flow {
  set_distname;
  create_distdir;
  create_maketool;
  create_general_files;
  create_tests;
  shipit( optional => 1 );
  create_files(qw( MainModule ));
  create_manifest;
  edit_mainfile( optional => 1 );
};

1;

Module::Newの実装(2)

複数の引数を与えてループさせるレシピはこんな感じ。

package Module::New::Recipe::File;

use strict;
use warnings;
use Module::New::Command;

available_options qw( edit|e type|t=s );

flow {
  guess_root;

  loop {
    set_file;
    create_files('{ANY_TYPE}');
    edit_mainfile( optional => 1 );
  };

  create_manifest;
};

1;

Module::Newの実装(3)

レシピを表現するのに使うコマンドの例。

sub create_maketool (;$) { my $type = shift; _register {
  my $self = shift;

  $type ||= $self->config('make') || 'MakeMaker';
  $type = 'ModuleInstall' if $type eq 'MI';
  $type = 'ModuleBuild'   if $type eq 'MB';
  $type = 'MakeMaker'     if $type eq 'EUMM';

  $self->context->add_files( $type );
}}

Module::Newの実装(4)

コマンドはグローバルなキューに登録して、最後にまとめて実行。

sub _register (&) {
  my $caller = caller(1);

  { no strict 'refs';
    push @{ ${"$caller\::queue"} ||= [] }, shift;
  }
}

sub flow (&) { my $flow = shift; my $caller = caller; _register {
  my ($self, @args) = @_;

  { no strict 'refs';
    my $queue = [];
    local ${"$caller\::queue"} = $queue;
    $flow->();  # let internal functions into the local queue

    foreach my $func ( @{ $queue } ) {
      $func->( $self, @args );
    }
  }
}}

Module::Newを拡張するときは(1)

まずは自前の名前空間を用意する。

package MyNew;

use strict;
use warnings;
use base qw( Module::New );
1;

Module::Newを拡張するときは(2)

その下に追加したいコマンドやレシピを用意して

package MyNew::Command::MyCommand;

use strict;
use warnings;
use Exporter::Lite;
use Module::New::Command::Util;

our @EXPORT = qw( some_command );

sub some_command () { _register{ .... }}

1;

Module::Newを拡張するときは(3)

package MyNew::Recipe::MyRecipe;

use strict;
use warnings;
use Module::New::Command;
use MyNew::Command::MyCommand;

flow {
  some_command;
};

1;

Module::Newを拡張するときは(4)

最後に起動スクリプトで自前の名前空間からdispatchすればOK。

#!perl
use strict;
use warnings;
use MyNew;

MyNew->dispatch;

自分で上書きしない限り、Module::Newのレシピもそのまま使える。

Module::Newがこんな風になっている理由(1)

アフターサービス重要というのももちろんありますが。

  • Pluggableにすればするほど定型のPODを書くのがだるくなる
  • DSLっぽくしてしまえばPODを書かなくてもすむ

というのが最大の理由。

Module::Newがこんな風になっている理由(2)

PODを書かずにすませる方法はいくつかある。

  • メソッド名をアンダースコアではじめる
  • Pod::Coverageが無視する予約語を使う
  • メソッドを動的に生成する

ただし、やみくもに省略するのはよくない。

  • 一般的にPODが充実していないモジュールは評価されない

DBICスキーマみたいに__PACKAGE__を使いまくる手もある。

でも、JDBIのスキーマと比べてどっちがいいか、という個人の趣味。

Module::Newがこんな風になっている理由(3)

ちなみにPODというのは

なので、常人の三倍のスピードを誇るニュータイプが相手なら気にしなくてもいいです。

Module::Newの問題点

いろいろあるけど

  • DSLっぽくつくるのであれば、@EXPORT(_OK)の処理はきちんとするべし
  • たいてい use base が使えなくなるので、Exporter(::Lite) などでフォローできるようにした方がいい

Module::Newの今後

  • スライドドリブン開発
  • POD
  • テスト
  • 依存まわりとか更新履歴とか

ご静聴ありがとうございました