11/13の関西闇Ruby会議での発表資料 <URL:http://www.kmc.gr.jp/~ohai/20111113/p.pdf>
subversion のパスワードが Gnome keyring の管理下にある場合に、 hgsubversion を使おうとすると、
Could not authenticate to server: rejected Digest challenge
などといったエラーがでる場合があります。
これは hgsubversion に問題があるのか subversion の python インターフェース に問題があるのかはわかりませんが、Gnome keyring管理下にある パスワードにアクセスする権限がない場合に発生します。 そのため権限を付加してやればうまく動作するようになります。
具体的には Gnome control center の「パスワードと暗号鍵」 から「パスワード: login」という項を右クリックし、「ロックの解除」を 選び、ロック解除用のパスワードを入力するとうまくいくようになります。
hgsubversion で subversion のリポジトリを mercurial のリポジトリとして clone したあと、 その mercurial リポジトリを再び別のリポジトリに clone すると、 hgsubversion のメタデータは複製されません。
しかし、hgsubversion はそこをうまくなんとかする方法があります。 やりかたは、.hg/hgrc を
[paths] default = /home/foo/bar
となっている(/home/foo/bar は複製元)のを、
[paths] default = svn+http://example.com/bar/
などのように svn のリポジトリの URL に変更し、
hg svn rebuildmeta
とすると、メタデータを復元します。
svn リポジトリのすべてのチェンジセットを clone などで複製するのは かなり時間がかかるため、他人が複製したものをこれで clone するなどすると かなり高速に svn リポジトリを取り込むことができます。
Ruby 1.9には、Enumerable#collectのようなブロックを取るメソッドに ブロックを渡さなかった場合、collectを実現するような繰り返しをする Enumerator オブジェクトを返します。これは、
[3, 4, 8].collect.with_index{|n,idx| n+idx} # => [3, 5, 10]
というような使い道があります。また Enumerator#with_object というのも あります。これは便利なのですが、どういう仕掛けになっているのか ちょっと不思議なところがあります。ここではそれについて解説します。 Rubyのソースコードを手元に置いて解説を呼んでください。
まずは、Enumerable#collectでブロックを与えた場合について見ていきます。 実装の本体は enum.c:enum_collect で
enum.c:
#define enum_yield rb_yield_values2
static VALUE
collect_i(VALUE i, VALUE ary, int argc, VALUE *argv)
{
rb_ary_push(ary, enum_yield(argc, argv));
return Qnil;
}
static VALUE
enum_collect(VALUE obj)
{
VALUE ary;
RETURN_ENUMERATOR(obj, 0, 0);
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, collect_i, ary);
return ary;
}
の部分です。 RETURN_ENUMERATOR はブロック省略時に Enumerator を返している 部分でしょうから、ここでは置いておきます。するとこのメソッドはおおよそ 次のようなことをしています。
module Enumerable
def collect
ary = Array.new
self.each{|n| ary.push(yield(n)); nil}
return ary
end
end
これで each メソッドが定義されたクラスで collect が使えるように なります。
では次に Enumerator を返すところを見ましょう。
include/ruby/intern.h:
#define RETURN_ENUMERATOR(obj, argc, argv) do { \
if (!rb_block_given_p()) \
return rb_enumeratorize((obj), ID2SYM(rb_frame_this_func()),\
(argc), (argv)); \
} while (0)
Rubyのコード片に直すと
if !block_given? return rb_enumeratorize(self, :collect) end
といった所でしょうか。マクロなのでこのコード片が enum_collect の所に 展開されるということに注意しましょう。 rb_frame_this_func は Ruby 側で呼びだしたメソッドの 名前を返しているので、ここでは collect になります。 argc, argv は collect では使っていない(0, 0になっている)ので無視します。 まあつまりブロックが渡されていない時に rb_enumeratorize の 返り値を返しているだけです。 次に rb_enumeratorize を見ます。
enumerator.c:
VALUE
rb_enumeratorize(VALUE obj, VALUE meth, int argc, VALUE *argv)
{
return enumerator_init(enumerator_allocate(rb_cEnumerator), obj, meth, argc, argv);
}
名前から予想するに、 enumerator_allocate は Enumerator 用のメモリを確保して、 Enumerator_init で初期化しているのでしょう。 次は enumerator_init を見ましょう。
enumerator.c:
static VALUE
enumerator_init(VALUE enum_obj, VALUE obj, VALUE meth, int argc, VALUE *argv)
{
struct enumerator *ptr;
TypedData_Get_Struct(enum_obj, struct enumerator, &enumerator_data_type, ptr);
if (!ptr) {
rb_raise(rb_eArgError, "unallocated enumerator");
}
ptr->obj = obj;
ptr->meth = rb_to_id(meth);
if (argc) ptr->args = rb_ary_new4(argc, argv);
ptr->fib = 0;
ptr->dst = Qnil;
ptr->lookahead = Qundef;
ptr->feedvalue = Qundef;
ptr->stop_exc = Qfalse;
return enum_obj;
}
ptr は先程確保されたメモリ領域を指しているので、これはおおよそ次の ようなことをしています。argc, argv は無視します。
def init(obj, meth)
@obj = obj
@meth = meth
@fib = 0
:
end
ここまでをまとめ、Rubyコードに直すと、以下のようになるでしょう
class Enumerator
def initialize(obj, meth)
@obj = obj
@meth = meth
end
:
end
def rb_enumeratorize(obj, meth)
return Enumerator.new(obj, meth)
end
module Enumerable
def collect
if !block_given?
return rb_enumeratorize(self, :collect)
end
ary = Array.new
self.each{|n| ary.push(yield(n)); nil }
return ary
end
end
最後に Enumerator#with_index が何をしているのかを見ましょう。
enumerator.c:
static VALUE
enumerator_block_call(VALUE obj, rb_block_call_func *func, VALUE arg)
{
int argc = 0;
VALUE *argv = 0;
const struct enumerator *e = enumerator_ptr(obj);
ID meth = e->meth;
if (e->args) {
argc = RARRAY_LENINT(e->args);
argv = RARRAY_PTR(e->args);
}
return rb_block_call(e->obj, meth, argc, argv, func, arg);
}
enumerator.c:
static VALUE
enumerator_with_index_i(VALUE val, VALUE m, int argc, VALUE *argv)
{
VALUE idx;
VALUE *memo = (VALUE *)m;
idx = INT2FIX(*memo);
++*memo;
if (argc <= 1)
return rb_yield_values(2, val, idx);
return rb_yield_values(2, rb_ary_new4(argc, argv), idx);
}
enumerator.c:
static VALUE
enumerator_with_index(int argc, VALUE *argv, VALUE obj)
{
VALUE memo;
rb_scan_args(argc, argv, "01", &memo);
RETURN_ENUMERATOR(obj, argc, argv);
memo = NIL_P(memo) ? 0 : (VALUE)NUM2LONG(memo);
return enumerator_block_call(obj, enumerator_with_index_i, (VALUE)&memo);
}
RETURN_ENUMERATORはとりあえずここでは用はないので無視、 memo は index がどこまで進んだかを表します。
return enumerator_block_call(obj, enumerator_with_index_i, &memo);
という意味になります。obj は先程生成した Enumerator オブジェクトです。 enumerator_block_call は e->obj と e->meth を取りだして、 rb_block_call に func を いっしょに渡し、呼びだしています。rb_block_callばブロック付き呼出を 実現するようなものですから、これは
class Enumerator
def enumerator_block_call(func)
@obj.__send__(@meth, &func)
end
end
に相当します。ここで @obj は 元は Enumerator#collect における self、 すなわち配列であり、@meth は :collect、funcは enumerator_with_index_i です。 enumerator_with_index_iですが、これは Ruby のコードに解釈すると
{|n| idx = memo; memo += 1; yield(n,i)}
というブロックを意味します。これを考えあわせて ruby のコードに直してみると、
class Enumerator
def enumerator_block_call(&func)
@obj.__send__(@meth, &func)
end
def with_index(memo=0)
enumerator_block_call{|n| idx = memo; memo += 1; yield(n,i)}
end
end
となります。結果として、
[3, 4, 8].collect.with_index{|n,idx| n+idx}
というコードは、
memo = 0
[3, 4, 8].collect{|n| idx=memo; memo+=1; n+idx}
と同等の動作をすることになります。
Enumerator#each はもう少し簡単で、
enumerator.c:
static VALUE
enumerator_each(VALUE obj)
{
if (!rb_block_given_p()) return obj;
return enumerator_block_call(obj, 0, obj);
}
やはり Ruby のコードで相当品を書くと、
class Enumerator
def each(&b)
return self if !block_given?
enumerator_block_call(&b)
end
end
ここで、rb_block_callのfuncの所にNULLを渡すとメソッドに 渡されたブロックを使うという事実によってこのような解釈ができます。 これは渡されたブロックを@obj.__send__(@meth)にたらい回ししているだけです。
最後に
ary = [1,2,3,4,5]
p ary.collect!.collect{|x| x%2 == 0}
p ary
というコードについて考えてみてください。
などすると、より理解が深まるでしょう。
忘れないうちに感想を。
Next version of Ruby 1.8 and 1.9 We will release Ruby: 2.0に 対するイメージははコアチームの中でも大きな差異があるようだ、と感じました。
Profiler: 発表では非常に完成度が高いように見えた。ruby-profとの性能差には何か 工夫があるのだろうか。
図書館 & クリティカルミッション: 実システムの話は聞いてておもしろい。
闇: 基調講演。
padrino良さそう。Webアプリケーションを作る予定はまったくないのですが。
Drip: 追記型アーキテクチャとtuple space的アイデアの融合、というのが とてもすばらしい。いや、tuple spaceからはかなり離れてしまっているんだけど。 なんか使ってみたいことです。
Mac Bookは外見が一様に見える(多様性がない)のが威圧感を与えるのではないか説。
lockの話: あのような話をあれだけわかりやすく話すのはすごい。何を話して 何を話さないかの選択が良いのかな。
rurima: るりまプロジェクトの一員として数言。1.リファレンスマニュアルで重要なことは 一通りすべての要素にドキュメントがあること。 まあTKとかやってられませんが。2. 基本的には時間と手をかければそれだけ進みます。 逆にそうしなければ進まないんですが。3.るりまプロジェクトが遅れているというのは まあそういう話です。
rubima: 長続きしているよなあ。インタビューは毎回楽しみにしております。
testing frameworkの作りかた: 判断基準を一貫することが重要という話。
O/R mapper: 「なるほど」というより「そうそうその通り」という。
参加してきました。すばらしい会議でした。 すべての関係者に感謝を。
LT の発表スライドを公開します。 <URL:http://www.kmc.gr.jp/~ohai/rubykaigi2011/p.pdf>
時間配分がうまくいかなかったので最後までいけませんでした。 この場合コードの所を(説明はできなかったかもしれませんが) 時間を取るようにしたほうがよかったかもしれません。
このネタは渾身の一作なんですが、あまりうけが良くなかった気がします。 面白がってくれる人の範囲が狭すぎたかもしれません。 5ページ目の「良く知られている」ことをよく知っている人でないと通じない 気がします。
何かどうにかしたい所です。
おまけ2の部分は発表後に思いついたものを追加してあります。