いろんな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

https://github.com/rubinius/rubinius/blob/4ecffe82ce19871600ee77e04d2e34b807424e13/core/enumerable.rb#L39L49

  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

https://github.com/opal/opal/blob/9d312baf7c6ad5460abf9b720dba07bc9c96f3a4/opal/corelib/enumerable.rb#L109-L125

  def enumerator_size
    respond_to?(:size) ? size : nil
  end

https://github.com/opal/opal/blob/9d312baf7c6ad5460abf9b720dba07bc9c96f3a4/opal/corelib/enumerable.rb#L645-L647

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));
        }
    }

https://github.com/jruby/jruby/blob/c4eb308eed3555618a35e78ef06d559ec0c804e3/core/src/main/java/org/jruby/RubyEnumerable.java#L889-L924

いいなと思ったらKyashでお金を下さい
20191128011151
GitHubスポンサーも受け付けています
https://github.com/sponsors/hanachin/