(続)implicit defが2回評価される件 → 正確には structural typingな戻り値に対するメソッド呼び出しの問題

前回の "なんで2回newされてるの?" については id:kmizushima さんや id:SiroKuro さん、 @kinaba さんといった識者の皆さんも想定外の動作だったようで、逆コンパイル(逆アセンブル)するという形で調査をされておりました。なるほど、こうやって追跡すればよかったですね。デバッグスキル足りなさすぎる。大変勉強になりました。

そして、その過程は皆さんのblogエントリを直接呼んでいただくとして、結論としては以下のようなものと思われるということでした。

結論として、今回の問題は、structural typeに対するメソッド呼び出しにおいて、レシーバーの式が2回評価されるという実装に起因するものだと言えそうです。

ふつうにバグのような予感。本当なら下のようになるはずなのにね。

Object object = hogeable("abc");
Class clazz = object.getClass();
Method method = reflMethod$Method1(clazz);
method.invoke(object);

確かにバグの可能性はあるというか、むしろそっちの方が可能性高い気もしなくもないけど、このレベルのバグが未だに残ってるもんかなーという疑問があったのでした。Structural Typeが入ったのって、もう2年以上前の話ですし。ともあれ、はっきりしないのは何なので、MLに質問投げてみました。

さらに追記2:

MLに投げた質問の返答が返ってきました。それによると、Scala 2.8ではFIXされてるよーとのこと。結局、バグだったってことみたいですね。

ああーっ、これバグだったのか… Scalaはまだ不慣れだということで「これはこれで正しいのだろう」と思っていました。

そして、私の元のエントリでは implicit def に限定した話のように見えましたが、そうではなく、structural typingなインスタンスを戻すメソッドの呼び出し、また戻り値を直接レシーバとしてメソッド呼び出しをするようなケースにおいてはいつでも起こりうる、ということのようです。

このstructural typingはScalaの強力さの源泉の一つでもあるので、このbugfixは2.8に期待することとして、置いておきましょう。

implicit def のケースは氏が指摘されているように、「副作用をおこさなければいい」と思われます。入出力、破壊操作などの副作用もそうですが、「時間がかかる」というような作用も2回働きますから注意したいところ。implicit defでパフォーマンスが問題になるようなコードを書くことはあり得ないというか、すべきでないでしょう。

識者の皆様、ありがとうございました!*1

*1:この手の問題を「まあそういう仕様もあるのかなあ」としておくのではなく、こう追いかけるのだ、という具体的なケースとして本当に勉強になりました