Hash Enumerable 方法:仅传递一个参数时的不一致行为

Hash Enumerable methods: Inconsistent behavior when passing only one parameter

Ruby 用于 Hash 的可枚举方法需要 2 个参数,一个用于键,一个用于值:

hash.each { |key, value| ... }

但是,我注意到当您只传递一个参数时,可枚举方法的行为不一致:

student_ages = {
"Jack" => 10,
"Jill" => 12,
}

student_ages.each { |single_param| puts "param: #{single_param}" }
student_ages.map { |single_param| puts "param: #{single_param}" }
student_ages.select { |single_param| puts "param: #{single_param}" }
student_ages.reject { |single_param| puts "param: #{single_param}" }

# results:

each...
param: ["Jack", 10]
param: ["Jill", 12]

map...
param: ["Jack", 10]
param: ["Jill", 12]

select...
param: Jack
param: Jill

reject...
param: Jack
param: Jill

如您所见,对于 eachmap,单个参数被分配给 [key, value] 数组,但对于 selectreject ,参数只有key.

这种行为是否有特殊原因?文档似乎根本没有提到这一点;所有给出的例子都假设你传递了两个参数。

我的猜测是内部 map 只是 eachcollect。有趣的是,它们的工作方式并不完全相同。

至于each...

源代码如下。它检查您传递给块的参数数量。如果不止一个它调用 each_pair_i_fast,否则只是 each_pair_i.

static VALUE
rb_hash_each_pair(VALUE hash)
{
    RETURN_SIZED_ENUMERATOR(hash, 0, 0, hash_enum_size);
    if (rb_block_arity() > 1)
    rb_hash_foreach(hash, each_pair_i_fast, 0);
    else
    rb_hash_foreach(hash, each_pair_i, 0);
    return hash;
}

each_pair_i_fast returns 两个不同的值:

each_pair_i_fast(VALUE key, VALUE value)
{
    rb_yield_values(2, key, value);
    return ST_CONTINUE;
}

each_pair_i 不:

each_pair_i(VALUE key, VALUE value)
{
    rb_yield(rb_assoc_new(key, value));
    return ST_CONTINUE;
}

rb_assoc_new returns 一个二元素数组(至少我假设那是 rb_ary_new3 所做的

rb_assoc_new(VALUE car, VALUE cdr)
{
    return rb_ary_new3(2, car, cdr);
}

select 看起来像这样:

rb_hash_select(VALUE hash)
{
    VALUE result;

    RETURN_SIZED_ENUMERATOR(hash, 0, 0, hash_enum_size);
    result = rb_hash_new();
    if (!RHASH_EMPTY_P(hash)) {
    rb_hash_foreach(hash, select_i, result);
    }
    return result;
}

select_i 看起来像这样:

select_i(VALUE key, VALUE value, VALUE result)
{
    if (RTEST(rb_yield_values(2, key, value))) {
    rb_hash_aset(result, key, value);
    }
    return ST_CONTINUE;
}

我将假设 rb_hash_aset returns 两个不同的参数类似于 each_pair_i

最重要的一点是 select/etc 根本不检查参数的元数。

来源:

刚刚检查了 Rubinius 的行为,确实与 CRuby 一致。所以看看 Ruby 的实现——确实是因为 #select yields two values:

yield(item.key, item.value)

同时 #each yields an array with two values:

yield [item.key, item.value]

将两个值生成一个块,该块期望一个接受第一个参数并忽略第二个参数:

def foo
  yield :bar, :baz
end

foo { |x| p x } # => :bar

如果块有一个参数,则 Yield 数组将被完全分配;如果有两个或更多参数,则 Yield 数组将被解包并分配给每个单独的值(就像你一个一个地传递它们一样)。

def foo
  yield [:bar, :baz]
end

foo { |x| p x } # => [:bar, :baz]

至于他们为什么做出这个决定 - 这背后可能没有任何充分的理由,只是没想到人们会用一个论据来打电话给他们。