【问题标题】: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

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

【问题讨论】:

    标签: ruby hash enumerable


    【解决方案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 根本不检查参数的数量。

    来源:

    【讨论】:

      【解决方案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,你会想要从最常用的类之一中最常用的方法。
      猜你喜欢
      • 1970-01-01
      • 2011-01-04
      • 1970-01-01
      • 2019-07-23
      • 1970-01-01
      • 2010-12-27
      • 1970-01-01
      • 1970-01-01
      • 2013-01-09
      相关资源
      最近更新 更多