時計を壊せ

駆け出してからそこそこ経ったWebプログラマーの雑記

間接オブジェクト記法の怪

怖い話

友人がこんなコードがうまく動かなくてハマっていました。


擬似コード

use strict;
use warnings;

# ...

sub hogemethod {
    # ...

    try {
        A;
    }
    catch {
        die $_;
#(comment out)        # ...
    };

    # ...
}

# ...

1;

このコードはtryの中で死ぬかもしれない処理Aをして、catchでエラーを受け取ってそのままdieする処理に見えます。
しかし、実際はtryを実行し終わったあと、catchも実行されてしまいます。


tryの中の処理が成功した場合はcatchは実行されない筈ですよね?
なぜ実行されてしまうのでしょうか。


答えは、Try::Tinyをuseしてなかったからです。


「じゃあ当たり前じゃん」と思った人はそのままブラウザバックするか、こんな時間にこんな記事読んでないで寝ると良いでしょう。
「なんで実行されるの。なにそれこわい。」と思った人は続きを読みましょう。

そのとき歴史が動いた

間接オブジェクト記法

突然ですが、Perlには間接オブジェクト記法というものがあります。
間接オブジェクト記法とは、あれです。あれ。

use MyClass;

# この記法です
my $text = get_text MyClass; # MyClass->get_text と等価

ちなみに、間接オブジェクト記法で、引数を渡すときは以下のようにします。

use MyClass;

# この記法です
my $text = get_text MyClass('Hoge', 'Fuga'); # MyClass->get_text('Hoge', 'Fuga') と等価

さらに、この間接オブジェクト記法はblessされたオブジェクトなどでも使う事ができます。

use MyClass;

# この記法です
my $obj  = new MyClass;
my $text = get_text $obj; # $obj->get_text と等価

さて、ここで、$objからcloneメソッドを呼んでもう一つMyClassオブジェクトを作り、
そのオブジェクトからget_textメソッドを呼んでみましょう。

use MyClass;

# この記法です
my $obj  = new MyClass;
my $text = get_text $obj->clone; # $obj->clone->get_text と等価?

おや、等価の後にクエッションマークが付いていますね。
答えを言ってしまえばこれは $obj->clone->get_text とは等価になりません。
正解は以下です。

use MyClass;

# この記法です
my $obj  = new MyClass;
my $text = get_text $obj->clone; # 本当は $obj->get_text->clone と等価

「じゃあ、変数に一度入れて渡すしかないじゃん。」と思ったあなた。
そんなあなたのためにPerlにはこんな方法も用意されています。

use MyClass;

# この記法です
my $obj  = new MyClass;
my $text = get_text { $obj->clone }; # $obj->clone->get_text と等価

よし。これで $obj->clone->get_text と等価になりました。めでたしめでたしですね。
あれ、こんなのどこかで見た事ありますね。

そうなんです

そうなんです。
try {} catch {};構文はそのまま書くと間接オブジェクト記法として解釈されてしまうんです。

use MyClass;

# この記法です
my $obj  = new MyClass;
my $text = try { $obj->clone }; # $obj->clone->try と等価

このコードは以下のように解釈されます。
(B::Deparseを活用するとPerlコードがどのようにPerlコンパイラに解釈されたかを知る事ができます)

use MyClass;
use warnings;
use strict 'refs';
my $obj = 'MyClass'->new;
my $text = do {
    $obj->clone
}->try;

ちなみに、Try::Tinyをuseした場合は以下のようになります。

use MyClass;
use Try::Tiny;
use warnings;
use strict 'refs';
my $obj = 'MyClass'->new;
my $text = try(sub {
    $obj->clone;
}
);

これは、Try::TinyからExportされたtryサブルーチンがプロトタイプ宣言を使ってCODEリファレンスを受け取るようにしている為、
このように解釈されるのです。


全然意味が違って来ますね!


なので、「おかしい!どこもおかしくない!」と思ったときは間接オブジェクト記法も疑ってみては如何でしょうか。
以上、間接オブジェクト記法怖いというお話でした。