はい、今回は「4.2 自動的な参照渡し」から読み続けます。
4.2 自動的な参照渡し
参照渡しにするには、いつも明示的にDRbObject.new()しなくてはならないのでしょうか?なんだか面倒ですね。 dRubyでは明示的に参照渡しをしなくても、ライブラリが自動的に判断して参照渡しと値渡しを選択する仕組みが用意されています。
dRubyでは次の規則で参照渡しと値渡しを選択します。
- Marshal.dumpできるものは値渡し
- Marshal.dumpできないものは参照渡し
4.2 自動的な参照渡し
ほうほう、今までは、参照渡しにするために明示的にDRbObject.newをしていました。ですが、Marshal.dumpできないものは参照渡しとなるわけですね、なるほど。本の「4.2.1 can't dump」あたりを読みながら実験してみます。本ではMarshal.dumpできない$stdoutを使って試していますね、では、本の通りにやってみます・・・はい、できました。(はやっ)
4.2.1 can't dump
front[:stdout]はIOではなく、DRbObjectとなりました。 DRbObjectの@uriはターミナル2のDRb.uriです。 front[:stdout]のDRbObjectはターミナル2のオブジェクトを参照するオブジェクトであることがわかります。
何がおきたのでしょうか?ターミナル2から$stdoutをターミナル1に渡す時、 dRubyのライブラリはMarshal.dumpが失敗したことを捉えて値渡しの代わりに参照渡しを行なったのです。
4.2.1 can't dump
にゃるほど、多分これに該当する箇所としてはDRbMessageの
private
def make_proxy(obj, error=false)
if error
DRbRemoteError.new(obj)
else
DRbObject.new(obj)
end
end
だと思います。みなさんも本の内容に沿って一緒にやってみて下さい。短いですがこのくらいで「4.2.1 can't dump」はおしまいにします。
4.2.2 DRbUndumped
では次。「4.2.2 DRbUndumped」に入ります。DRbUndumpedとは、Marshal.dumpは成功するからそのままやっちゃうと値渡しになるんだけど、参照渡しにしたい。ってときに使うもののようです。
いつも参照渡しとなるように、Marshal.dumpを失敗させるための Mixinモジュール DRbUndumped を用意しています。DRb::DRbUndumped(別名DRbUndumpedも使えます)は、クラスにincludeしたりオブジェクトにextendして使用するモジュールです。
4.2.2 DRbUndumped
こちらも本に載っている手順の通りにやってみます。簡単ですね。
foo.extend(DRbUndumped)
とすると、そのオブジェクトが参照渡しになります。また、
class Foo
include DRbUndumped
とすると、そのクラスから生成されるオブジェクトは常に参照渡しになるようです。はい、よくわかりました。
でも、ちょっと気になりませんか。このDRbUndumpedモジュールは常にMarshal.dumpを失敗させるとのことですが、一体どんな方法で仕組みを使って失敗させているのでしょう。気になるのでDRbUndumpedのソースを見てみます。
module DRbUndumped
def _dump(dummy) # :nodoc:
raise TypeError, 'can\'t dump'
end
end
これだけですか、なんでこれでMarshal.dumpできないようになるのか全然わかりません。だいたい_dumpって何?むぅぅ、、ってことでRubyのリファレンスマニュアルを見てみます。
Marshal.dump において出力するオブジェクトがメソッド `_dump' を定義している場合には、そのメソッドの結果が書き出されます。メソッド `_dump' は引数として再帰を制限するレベル limit を受け取り、オブジェクトを文字列化したものを返します。
とありました。なるほど、Marshal.dumpの中で_dumpが必ず呼ばれるんですね。そりゃそうだろうなあ。Marshalの中を見て一応確かめてみることにします。。と、ソースを見ようと思ったらこいつはCで書かれているのか。marshal.cというソースファイルのようですね。Ruby本体のソースは全然構造や関数の用途を把握していないので全くわかんないけど。。。えーっと、_dumpはどこかなと
void
Init_marshal()
{
・・・
s_dump = rb_intern("_dump");
・・・
いました。これですね。rb_internというのがよくわかんないけど、付近のコードを見ても定義を行なっているだけの雰囲気があるので、"_dump"という文字列に対応するような識別子がs_dumpに設定されて特定できるようになっているのでしょう多分。
じゃあ次にMarshal.dumpで呼ばれる部分にあたりをつけてみます。。と、あったあった、おそらくこの部分だと思います。
static VALUE
dump(arg)
struct dump_call_arg *arg;
{
w_object(arg->obj, arg->arg, arg->limit);
if (arg->arg->dest) {
rb_io_write(arg->arg->dest, arg->arg->str);
rb_str_resize(arg->arg->str, 0);
}
return 0;
}
ここで呼ばれているw_objectの中も見てみます。この関数は、220行を超える結構長めの関数です。型などに応じて処理を分けるcaseがあるからデカいようですね。で、この中で
if (rb_respond_to(obj, s_dump)) {
VALUE v;
v = rb_funcall(obj, s_dump, 1, INT2NUM(limit));
・・・
としています。rb_respond_toは、おそらくRubyのrespond_to?と同じようなものでしょう。Rubyのrespond_to?は、引数の名前のメソッドを持っているかどうかを問うものです。で、rb_funcallは、名前からして処理を呼び出す(実行する)ものだと思われますから、ここでの処理は、
_dumpと言う名前のメソッドがobjにあれば、それを実行しなさい
と言うことだと思います。全て勝手な推測ばかりですが。一応_dumpを呼んでそうな気はしました。しかしRubyのソースを読めるようになるまでには多分かなりの時間がかかるだろうなあ。と思ってたら、これを見ると色々詳しくなるらしいと聞きました。が、まだ私は読めてません。
なんか今回はかなり脱線してしまいました。
ので、ちょっと纏めます。
今回のエントリのまとめ
今日のところはこれで終了です。次回は「4.3 未知のオブジェクトとDRbUnknown」に入りたいところですが、ページ数も短いのでさらっと読んだ後に「4章.参照渡しと値渡し」のまとめっぽい簡単なスクリプトを書きたいと思っています。次回で4章が終わる予定です。
コメント (2)
4.2.1 can't dump、dumpの失敗を捉えて‥っぽいコードはこの辺じゃないかと思うデス。
def dump(obj, error=false) # :nodoc:
obj = make_proxy(obj, error) if obj.kind_of? DRbUndumped
begin
str = Marshal::dump(obj)
rescue
str = Marshal::dump(make_proxy(obj, error))
end
[str.size].pack('N') + str
end
投稿者: 咳 | 2007年10月31日 21:54
日時: 2007年10月31日 21:54
あ、説明するんだったらmake_proxyの中身じゃなくdumpの方が適切でした。コメントありがとうございます。(初コメントだーわーい)
投稿者: えがぴ~ | 2007年11月02日 16:00
日時: 2007年11月02日 16:00