Arrayにメソッドを追加して遊んでみる - ore-public/ruby GitHub Wiki

Array#secondを実装してみる

array.cのrb_ary_firstの実装を参考に書いてみる。

https://github.com/ore-public/ruby/commit/07edb808c9f0b141046b5ef1afc80c86eb6d7473

rbenv管理になるようにインストールする

% autoconf
% ./configure --prefix=$HOME/.rbenv/versions/array_second
% make
% make install

動かしてみる

% rbenv shell array_second
% irb
irb(main):001:0> [1,2].second
=> 2
irb(main):002:0> [1].second
=> nil
irb(main):003:0>

だいたい出来てる。面白い。

引数のチェック

Array#firstは引数を0 or 1個取るメソッド。2個以上のメソッドを渡すとArgumentErrorとなる。

irb(main):007:0> [1].first(1,2,3,4,5)
ArgumentError: wrong number of arguments (5 for 1)
	from (irb):7:in `first'
	from (irb):7
	from ./bin/irb:11:in `<main>'

Array#secondは引数をいくら渡してもエラーにならない。

irb(main):006:0> [1].second(1,2,3,4,5)
=> nil

ArgumentErrorはどこで、どう実装されているのか調べてみる。 Array#secondは引数無しの仕様ということにするので、一つ以上引数を渡したら同じようにエラーを出したい。

実装を追いかけてみる

Array#firstの実装を見る。

/* array.c */
static VALUE
rb_ary_first(int argc, VALUE *argv, VALUE ary)
{
    if (argc == 0) {
        if (RARRAY_LEN(ary) == 0) return Qnil;
        return RARRAY_AREF(ary, 0);
    }
    else {
        return ary_take_first_or_last(argc, argv, ary, ARY_TAKE_FIRST);
    }
}

引数がある時は、ary_take_first_or_lastが呼ばれている。 そっちの実装を探してみる。

/* array.c */
static VALUE
ary_take_first_or_last(int argc, const VALUE *argv, VALUE ary, enum ary_take_pos_flags last)
{
    VALUE nv;
    long n;
    long len;
    long offset = 0;

    rb_scan_args(argc, argv, "1", &nv);
    n = NUM2LONG(nv);
    len = RARRAY_LEN(ary);
    if (n > len) {
        n = len;
    }
    else if (n < 0) {
        rb_raise(rb_eArgError, "negative array size");
    }
    if (last) {
        offset = len - n;
    }
    return ary_make_partial(ary, rb_cArray, offset, n);
}

rb_scan_argsって関数が名前的に怪しい気がする。

http://docs.ruby-lang.org/ja/2.2.0/function/rb_scan_args.html

このドキュメント見た感じ、やっぱりこれが該当っぽい。

Array#secondの実装に、勘を交えつつ入れてみる。

https://github.com/ore-public/ruby/commit/3e3f8de72fbeb04d34c680a56bc56f75450b25b2

これで、ビルドして動かしてみる。

irb(main):003:0> [1,2].second(1)
ArgumentError: wrong number of arguments (1 for 0)
	from (irb):3:in `second'
	from (irb):3
	from ./bin/irb:11:in `<main>'

OK!

具体的にどういう処理をしているのか気になるの

さらにrb_scan_argsの実装を追いかけてみる。

・・・ちょっと長かった。

実装を追いかける前に、どういう処理なのかもう少し詳しくみる。明らかに引数の数をチェックだけしてる関数ではない。

/* class.c */
int
rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...)
{
    int i;
    const char *p = fmt;
    VALUE *var;
    va_list vargs;
    int f_var = 0, f_hash = 0, f_block = 0;
    int n_lead = 0, n_opt = 0, n_trail = 0, n_mand;
    int argi = 0;
    VALUE hash = Qnil;

    if (ISDIGIT(*p)) {
        n_lead = *p - '0';
        p++;
        if (ISDIGIT(*p)) {
            n_opt = *p - '0';
            p++;
            if (ISDIGIT(*p)) {
                n_trail = *p - '0';
                p++;
                goto block_arg;
            }
        }
    }
    if (*p == '*') {
        f_var = 1;
        p++;
        if (ISDIGIT(*p)) {
            n_trail = *p - '0';
            p++;
        }
    }
  block_arg:
    if (*p == ':') {
        f_hash = 1;
        p++;
    }
    if (*p == '&') {
        f_block = 1;
        p++;
    }
    if (*p != '\0') {
        rb_fatal("bad scan arg format: %s", fmt);
    }
    n_mand = n_lead + n_trail;

    if (argc < n_mand)
        goto argc_error;

    va_start(vargs, fmt);

    /* capture an option hash - phase 1: pop */
    if (f_hash && n_mand < argc) {
        VALUE last = argv[argc - 1];

        if (NIL_P(last)) {
            /* nil is taken as an empty option hash only if it is not
               ambiguous; i.e. '*' is not specified and arguments are
               given more than sufficient */
            if (!f_var && n_mand + n_opt < argc)
                argc--;
        }
        else {
            hash = rb_check_hash_type(last);
            if (!NIL_P(hash)) {
                VALUE opts = rb_extract_keywords(&hash);
                if (!hash) argc--;
                hash = opts ? opts : Qnil;
            }
        }
    }
    /* capture leading mandatory arguments */
    for (i = n_lead; i-- > 0; ) {
        var = va_arg(vargs, VALUE *);
        if (var) *var = argv[argi];
        argi++;
    }
    /* capture optional arguments */
    for (i = n_opt; i-- > 0; ) {
        var = va_arg(vargs, VALUE *);
        if (argi < argc - n_trail) {
            if (var) *var = argv[argi];
            argi++;
        }
        else {
            if (var) *var = Qnil;
        }
    }
    /* capture variable length arguments */
    if (f_var) {
        int n_var = argc - argi - n_trail;

        var = va_arg(vargs, VALUE *);
        if (0 < n_var) {
            if (var) *var = rb_ary_new4(n_var, &argv[argi]);
            argi += n_var;
        }
        else {
            if (var) *var = rb_ary_new();
        }
    }
    /* capture trailing mandatory arguments */
    for (i = n_trail; i-- > 0; ) {
        var = va_arg(vargs, VALUE *);
        if (var) *var = argv[argi];
        argi++;
    }
    /* capture an option hash - phase 2: assignment */
    if (f_hash) {
        var = va_arg(vargs, VALUE *);
        if (var) *var = hash;
    }
    /* capture iterator block */
    if (f_block) {
        var = va_arg(vargs, VALUE *);
        if (rb_block_given_p()) {
            *var = rb_block_proc();
        }
        else {
            *var = Qnil;
        }
    }
    va_end(vargs);

    if (argi < argc) {
      argc_error:
          rb_error_arity(argc, n_mand, f_var ? UNLIMITED_ARGUMENTS : n_mand + n_opt);
    }

    return argc;
}
⚠️ **GitHub.com Fallback** ⚠️