散列可枚举方法:仅传递一个参数时的行为不一致

时间:2016-01-18 22:20:53

标签: ruby hash enumerable

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

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

2 个答案:

答案 0 :(得分:1)

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

关于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返回两个不同的值:

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返回一个两元素数组(至少我假设这是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返回两个与each_pair_i类似的不同参数。

最重要的是注意select / etc根本不检查参数arity。

来源:

答案 1 :(得分:1)

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

yield(item.key, item.value)

while #each yields an array with two values

yield [item.key, item.value]

为块生成两个值,期望一个取第一个参数并忽略第二个参数:

def foo
  yield :bar, :baz
end

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

如果块有一个参数,则生成一个数组将被完全赋值,或者如果有两个或多个参数,则获取解包并分配给每个单独的值(如果你逐个传递它们)。

def foo
  yield [:bar, :baz]
end

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

至于他们为什么做出这种决定 - 可能背后没有任何好理由,只是不期望人们用一个论点来称呼他们。