メソッドチェーンの話

たしかに使い方によっては非常に読みづらくなるのは事実だから、「あまり多用しないでほしい」という気持ちもわからないではない。

でも、このサンプルでそれを言うのはあんまりじゃなかろうか。

 以下のようなメソッドチェーンは読むのが困難だ。

my $loader = Loader->new;
my $book = $loader->load( 'Book' )->build

 loadメソッドが自分自身( $loader )を返却して、そこからbuildが呼ばれたのか、他のオブジェクトが返却され、そこからbuildがよばれたのかがわからない。

 だから、あんまりメソッドチェーンを多用しないでほしい。Mojoソースコードを読んでいてそう思った。

ふたつめの文をそのまま英語として、名詞と動詞に注目しながら読んでみてほしい。「$bookは、$loaderがBookをロードしてビルドしたもの」と、実にすっきり読めないだろうか。

※追記:ちなみにこのサンプル、Mojoの文脈だとbuildの返り値は\@instancesなので、本当は正確ではない(つまり、$bookはBookのオブジェクトではない)。あくまでサンプルと割り切ってください。

もっと長い例を紹介しよう。MojoX::Dispatcher::Staticの一節だ。

my @parts = @{$c->req->url->path->clone->canonicalize->parts};

これも、名詞と動詞のつながりに注目しながら読んでほしい。「@partsは、$cのreq(uest)のurlのpathが(自身を)クローンして、正規化したもののpartsを配列化したもの」と訳せるだろうか。人によってはcloneを名詞に読んだかもしれないし、pathのあとを「pathが(自身を)」ではなく「pathを」と読んだかもしれないが、実用面ではそう大きな違いはないだろう(実装面では大きな違いだが)。

Mojoのメソッドチェーンは、このような、英語としての読みやすさを実現するために用意されている。

最初のローダの例でいうと、もし仮にload('Book')がロードしたパッケージ(MyApp::Book)あるいはそのオブジェクトを返すような実装になっていたら、ロードされたパッケージがbuildというメソッドを持っていることが前提になってしまう。もしMojoの実装がそのような暗黙の了解を求めるものであったなら、load->buildというメソッドチェーンに対する文句を言うのもありかもしれない。

でも、実際には、Mojoのローダはloadしたモジュールを内部に蓄えているので、モジュールの実装によらずbuildはできる(成功するかどうかはともかくとして)。

これを、たとえばよくあるこんな実装と比べてみてほしい。慣れの問題はさておき、buildという動詞がもっぱら他動詞で使われることを了解している人にとって、どちらがより直感的だろうか。

my $loader = Loader->new;
my $object = $loader->load('Book');
   $object->build;  # $objectが目的語だとしたら、主語は?

実はわれわれが何気なく使っているnewというコンストラクタもそう。最近はみんなクラスに対するメソッドとして使っているけど、これも本来は

my $object = new Class;

という英語らしい構文を実現するために生まれたものだ。O/Rマッパによく見られるhas_aとかhas_manyもそう。$author->has_a('book')という文を見て、$authorというオブジェクトが主語と感じない人はいないだろう。

ちなみに、Mojo内部に$loader->load($module)->build的なコードはほとんど出てこない(手元の版ではMojo::Scriptsに一カ所、load->buildという具合に目的語をはさまないものがあるだけ)。実際にはもっと自然な英語になるよう、$loader->load_build($module)となっている。load_and_buildにしなかったわけは、さらにそのあとにload_build($module)->runと続くことがあるから、と言えば十分だろうか。

最初はとまどうかもしれない。けれど、一頃はやったDSLっぽいAPIに限らず、最近の良質なコードはこういうところにまで気を遣って書かれているということを認識してもらえればいいなと思う。