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
如您所见,对于 each
和 map
,单个参数被分配给 [key, value]
数组,但对于 select
和 reject
,参数只有key
.
这种行为是否有特殊原因?文档似乎根本没有提到这一点;所有给出的例子都假设你传递了两个参数。
我的猜测是内部 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
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]
至于他们为什么做出这个决定 - 这背后可能没有任何充分的理由,只是没想到人们会用一个论据来打电话给他们。
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
如您所见,对于 each
和 map
,单个参数被分配给 [key, value]
数组,但对于 select
和 reject
,参数只有key
.
这种行为是否有特殊原因?文档似乎根本没有提到这一点;所有给出的例子都假设你传递了两个参数。
我的猜测是内部 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
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]
至于他们为什么做出这个决定 - 这背后可能没有任何充分的理由,只是没想到人们会用一个论据来打电话给他们。