哈希可枚举方法:仅传递一个参数时的行为不一致
Posted
技术标签:
【中文标题】哈希可枚举方法:仅传递一个参数时的行为不一致【英文标题】:Hash Enumerable methods: Inconsistent behavior when passing only one parameter 【发布时间】:2016-04-24 05:43:06 【问题描述】:Hash
的 Ruby 可枚举方法需要 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
。
这种行为有什么特殊原因吗?文档似乎根本没有提到这一点。给出的所有示例都假设您传入了两个参数。
【问题讨论】:
【参考方案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 根本不检查参数的数量。
来源:
https://github.com/ruby/ruby/blob/d5c5d5c778a0e8d61ab07669132dc18fb1a2e874/hash.c https://github.com/ruby/ruby/blob/9f44b77a18d4d6099174c6044261eb1611a147ea/array.c【讨论】:
【参考方案2】:刚刚检查了 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
如果块有一个参数,则生成一个数组将被完全分配;如果有两个或多个参数,则将被解包并分配给每个单独的值(就像您一个一个地传递它们一样)。
def foo
yield [:bar, :baz]
end
foo |x| p x # => [:bar, :baz]
至于他们做出这个决定的原因——这背后可能没有任何充分的理由,只是没想到人们会用一个论据来称呼他们。
【讨论】:
谢谢。这很清楚为什么这些方法会以这种方式运行。至于他们为什么做出这个决定:如果他们没想到人们会用一个参数来调用他们,为什么不直接不允许它并抛出一个 Argument Error 呢?对我来说,他们要么这样做,要么在所有方法中保持一致...... @dwenzel,blocks 对他们的论点往往更宽容。如果传递了更多值,则需要 - 忽略剩余值。如果需要更少的值 - 缺失值将被视为nil
。验证该块是否需要确切数量的参数(其本身是统一的)并引发异常的唯一方法是将其转换为 proc 并调用Proc#arity
。但是,这带有重要的performance hit,这不是什么
@dwenzel,你会想要从最常用的类之一中最常用的方法。以上是关于哈希可枚举方法:仅传递一个参数时的行为不一致的主要内容,如果未能解决你的问题,请参考以下文章
c#错误不一致的可访问性:参数类型'HRDMSV1.User'比方法更难访问