いろんなRubyのmapの実装
ruby-jpのSlackでmapとeachの関係を知りたいと書かれていたのに反応してつい調べた。ruby-jpのログは消えてしまうのでここに書いておく。 https://ruby-jp.slack.com/archives/CLZB6ACR4/p1566637551430500
ruby
ruby、組み込みライブラリはCで書かれている。
組み込みライブラリは Ruby 本体に組み込まれているライブラリです。 このライブラリに含まれるクラスやモジュールは、 require を書かなくても使うことができます。 https://docs.ruby-lang.org/ja/latest/library/_builtin.html
/* * call-seq: * enum.collect { |obj| block } -> array * enum.map { |obj| block } -> array * enum.collect -> an_enumerator * enum.map -> an_enumerator * * Returns a new array with the results of running <em>block</em> once * for every element in <i>enum</i>. * * If no block is given, an enumerator is returned instead. * * (1..4).map { |i| i*i } #=> [1, 4, 9, 16] * (1..4).collect { "cat" } #=> ["cat", "cat", "cat", "cat"] * */ static VALUE enum_collect(VALUE obj) { VALUE ary; int min_argc, max_argc; RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size); ary = rb_ary_new(); min_argc = rb_block_min_max_arity(&max_argc); rb_lambda_call(obj, id_each, 0, 0, collect_i, min_argc, max_argc, ary); return ary; }
https://github.com/ruby/ruby/blob/97ad7862d5f65c48409a5301609af8448aaae16d/enum.c#L552-L582
static VALUE collect_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, ary)) { rb_ary_push(ary, rb_yield_values2(argc, argv)); return Qnil; }
https://github.com/ruby/ruby/blob/97ad7862d5f65c48409a5301609af8448aaae16d/enum.c#L535-L541
mruby
mrubyはRubyで書かれていて分かりやすい。ISOのRubyのどこを実装しているのかもコメントで書かれていてべんりだ。
## # Call the given block for each element # which is yield by +each+. Append all # values of each block together and # return this value. # # ISO 15.3.2.2.3 def collect(&block) return to_enum :collect unless block ary = [] self.each{|*val| ary.push(block.call(*val))} ary end
https://github.com/mruby/mruby/blob/71242c0f2e1c07b693baf12ead8384dbfd120cb5/mrblib/enum.rb#L52-L65
rubinius
rubiniusもRubyで書かれていてべんり。
enumerator_size
はプライベートメソッドで補助的に定義されてるやつ。to_enum
にブロック渡したときの挙動しらなかった。
ブロックを指定した場合は Enumerator#size がブロックの評価結果を返します。 https://docs.ruby-lang.org/ja/latest/method/Object/i/enum_for.html
def collect if block_given? ary = [] each do |*element| ary << yield(*element) end ary else to_enum(:collect) { enumerator_size } end end
def enumerator_size Rubinius::Type.object_respond_to?(self, :size) ? size : nil end private :enumerator_size
Opal
こちらもRubyで書かれている。rubinius同様enumerator_size
が定義されているがプライベートメソッドではないよう(コントリビュートチャンスか?)
%x
、バッククオートで囲むやつのパーセント版で、Opalの文脈だと外部言語のJavaScript呼んでいる。$each
を呼ぶ前に$$p
を設定してから呼ぶインターフェイスなのかな? pなのはproc
の略なんだろうか、Opalの実装を読みたくなってきた。
def collect(&block) return enum_for(:collect) { enumerator_size } unless block_given? %x{ var result = []; self.$each.$$p = function() { var value = Opal.yieldX(block, arguments); result.push(value); }; self.$each(); return result; } end
def enumerator_size respond_to?(:size) ? size : nil end
jruby
Javaで実装されている。アノテーションでメソッド名が定義されてるんだなあ。スレッドセーフみがある。
@JRubyMethod(name = "map") public static IRubyObject map(ThreadContext context, IRubyObject self, final Block block) { return collectCommon(context, self, block, "map"); } private static IRubyObject collectCommon(ThreadContext context, IRubyObject self, final Block block, String methodName) { final Ruby runtime = context.runtime; if (block.isGiven()) { final RubyArray result = runtime.newArray(); callEach19(runtime, context, self, block.getSignature(), new BlockCallback() { public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) { final IRubyObject larg; boolean ary = false; switch (largs.length) { case 0: larg = ctx.nil; break; case 1: larg = largs[0]; break; default: larg = RubyArray.newArrayMayCopy(ctx.runtime, largs); ary = true; } IRubyObject val = ary ? block.yieldArray(ctx, larg, null) : block.yield(ctx, larg); synchronized (result) { result.append(val); } return ctx.nil; } @Override public IRubyObject call(ThreadContext ctx, IRubyObject larg, Block blk) { IRubyObject val = block.yield(ctx, larg); synchronized (result) { result.append(val); } return ctx.nil; } }); return result; } else { return enumeratorizeWithSize(context, self, methodName, enumSizeFn(context, self)); } }