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

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

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

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

【问题讨论】:

【参考方案1】:

我的猜测是内部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 返回两个不同的值:

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'比方法更难访问

iOS 14 中设备之间的小部件行为不一致

重载&方法返回值

计算格兰杰系数时的不一致参数

jqueryUI 可排序/可拖动和溢出(或 z-index?) - 不一致的行为

一致性哈希的实现