Moose::Roleをちょろっと使ってみたよ

Perl界隈では空前のMooseブームが起こっていて良いですね! そういえば,卒論で書いたコードでMooseを使っていたのを思い出したので,最近,使い方を把握したMoose::Roleを使ってリファクタリングしてみました.

とか思って,書いてみましたが,実際書いてみるとMoose::Roleの使い方としてはすごく微妙になってしまいました!概念として継承関係になっているのを強引にmix-inにするとかわけわからないです!なので,以下若干ぐだぐだ.

リファクタリング前のコード

リファクタリングの対象とするのは,抽象的なAST Parserを実装したクラスです.

対象言語に依存しないような抽象的なParserクラスとしてNyanco::AST::Parseを定義しています,

package Nyanco::AST::Parser;
use Moose;
use Nyanco::AST::Code;

sub parse {
    my ($self, $source) = @_;

    my $content = $source->read();
    my $root = $self->_create_tree($content);
    
    return Nyanco::AST::Code->new( root => $root, source => $source);
}   

sub _create_tree { confess "You should override this method."; }

no Moose;1;

このクラスでは,parseメソッドを呼ぶと,privateな_create_treeを使ってASTを作ります.Nyanco::AST::CodeがASTの実態クラスです.これは抽象クラスなので,_create_treeは実装されていなくて,例外を出すしかしていません.

これの実装のクラスとして,Nyanco::AST::Parser::Perlがあります.Perlのソースコードを読み込んでASTをつくります.

package Nyanco::AST::Parser::Perl;
use Moose;
use Nyanco::AST::Node::Token;
use Nyanco::AST::Node::Block;
use PPI;

extends qw(Nyanco::AST::Parser);

sub _create_tree {
    my ($self, $content) = @_;
    my $ppidoc = PPI::Document->new(\$content);

    my $root = $self->_walk($ppidoc);

    return $root;
}  

sub _walk {
  # PPIでがんばってASTっぽいツリーをつくる!
}

no Moose; 1;

このクラスでは,Nyanco::AST::Parserを継承して_create_treeメソッドを実装しています.Nyanco::AST::Parserのparseメソッドは継承しているので,

Nyanco::AST::Parser::Perl->new->parse( $source )

というように,parseメソッドを使うことができます.

リファクタリング

さて,Nyanco::AST::ParserはMoose::Roleとして扱えそうです.Moose::Roleは複数のクラスの共通の機能を切り出して,ほかのクラスで利用できるようにする仕組みです.OOの継承よりもmix-inの概念に近そうですね.というわけで,前述のNyanco::AST::ParserをRoleに書き換えてみます.

package Nyanco::AST::Parser;
use Moose::Role;
use UNIVERSAL::require;
use Nyanco::AST::Code;

requires '_create_tree';

sub parse {
    my ($self, $source) = @_;

    my $content = $source->read();
    my $root = $self->_create_tree($content);
    
    return Nyanco::AST::Code->new( root => $root, source => $source);
}       
    
1;

例外を送出するだけの,意味のない_create_treeメソッドを書く必要がなくなりました.かわりに requires '_create_tree' が増えてこのRoleを利用する場合_create_treeメソッドの実装が必要であることを示しています.

Nyanco::AST::Parser::Perlのほうというと,

package Nyanco::AST::Parser::Perl;
use Moose;
use Nyanco::AST::Node::Token;
use Nyanco::AST::Node::Block;
use PPI;

with qw(Nyanco::AST::Parser);

sub _create_tree {
    my ($self, $content) = @_;
    my $ppidoc = PPI::Document->new(\$content);

    my $root = $self->_walk($ppidoc);

    return $root;
}  

sub _walk {
  # PPIでがんばってASTっぽいツリーをつくる!
}

no Moose; 1;

となります.extends qw(Nyanco::AST::Parser); が with qw(Nyanco::AST::Parser); になっただけですね.継承からRoleへの変更です.

リファクタリング完了

とりあえず,Moose::Roleを使って継承で実現していたことをやってみました.Moose::Roleを使った利点としては,

  • 例外を出すだけのかっこわるい抽象メソッドが不要
  • 抽象メソッドが明言されているのでわかりやすい
  • 抽象クラスとして定義されるのでインスタンス化を防げる

とかが考えられそうです.

しかし,今回の例はあまり良くなくて,もともと継承関係で定義してすっきりしていた設計が,Roleをつかったmix-inっぽくなってかなり気持ち悪くなってしまいました.Moose::Cookbook::Recipe10にあるような,比較などのもうすこし一般的な概念をRoleとして取り出すほうがよさげです.

ただ,Javaっぽい抽象クラスやインターフェース機能はあるとうれしいので,Mooseの機能でRole以外の抽象クラスの実現方法があったらぜひ使いたいところです.だれか教えてー.