Ruby 2.2 Hash#拒绝返回Hash继承类?

时间:2015-11-10 20:11:19

标签: ruby

在从Ruby 1.9.3升级到Ruby 2.2.3(MRI)的过程中,我发现了一个影响从Hash继承的任何类的问题。如果在继承自#reject的类的实例上调用Hash,它将始终返回Hash而不是调用它的类的实例。

例如,给出以下代码:

class CustomHash < Hash
  def count_in_english
    "There are #{self.count} items in this hash."
  end
end

对于Ruby 1.9.3,以下成功:

1.9.3-p547 :060 > hash = CustomHash.new
 => {} 
1.9.3-p547 :061 > hash[1] = 'a'
 => "a" 
1.9.3-p547 :062 > hash[2] = 'b'
 => "b" 
1.9.3-p547 :063 > hash[3] = 'c'
 => "c" 
1.9.3-p547 :064 > odds_only_hash = hash.reject { |k,v| k % 2 == 0 }
 => {1=>"a", 3=>"c"} 
1.9.3-p547 :065 > odds_only_hash.count_in_english
 => "There are 2 items in this hash."
1.9.3-p547 :066 > odds_only_hash.class
 => CustomHash

但是在Ruby 2.2.3中:

2.2.3 :019 > hash = CustomHash.new
 => {} 
2.2.3 :020 > hash[1] = 'a'
 => "a" 
2.2.3 :021 > hash[2] = 'b'
 => "b" 
2.2.3 :022 > hash[3] = 'c'
 => "c" 
2.2.3 :023 > odds_only_hash = hash.reject { |k,v| k % 2 == 0 }
 => {1=>"a", 3=>"c"} 
2.2.3 :024 > odds_only_hash.count_in_english
NoMethodError: undefined method `count_in_english' for {1=>"a", 3=>"c"}:Hash
    from (irb):24
    from /Users/davidelner/.rvm/rubies/ruby-2.2.3/bin/irb:15:in `<main>'
2.2.3 :025 > odds_only_hash.class
 => Hash

进行一些搜索后,它看起来像this is known by the Ruby devsdiscussed a little bit,并在this blog post中详细说明。这种变化也打破了Rails&#39; HashWithIndifferentAccess根据this issuea pull request for Rails 4发布了{{3}}(但在Rails 3.2.22中仍未解决?)

显然,这种行为让很多人措手不及,考虑到它如何打破已知的Ruby宝石世界(包括Rails,Hashie等)依赖于对象不应该出乎意料的基本观点,这种变化听起来很荒谬改变类型。

对于一些知情的Ruby开发者来说,我的问题是:

  • Ruby是否承诺在Ruby 2.2.3和所有未来版本Hash#reject将始终返回Hash? (与调用此函数的类的实例相反?例如1.9.3 C源return rb_hash_delete_if(rb_obj_dup(hash));
  • 如果是这样,为什么现在这个默认行为?这不是有效的,而是密封的。 #reject,打破合理的期望,使用这些Enumerable函数仍会返回相同类型的对象?
  • 此外,如果是这样,开发人员如何能够适应这种行为变化? (我们都期望完全像Rails团队那样做吗?)

1 个答案:

答案 0 :(得分:2)

CAVEAT! 我不是一个更好的Ruby开发者&#34;这是我第一次看到这个问题。

查看提交历史记录,这是一个刻意的改变。

commit 740535f843d65be45732e45b9fc07eadc4d63ba7
Author: nobu <nobu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>
Date:   Wed Dec 11 07:01:29 2013 +0000

    hash.c: reject should return a plain hash

    * hash.c (rb_hash_reject): return a plain hash, without copying
      the class, default value, instance variables, and taintedness.
      they had been copied just by accident.
      [ruby-core:59045] [Bug #9223]

Bug #9223Matz accepting the change of Hash#reject

  

我接受这种行为改变。 #reject不应该像#select一样复制实例变量等。

它似乎是打算用于2.2,但从2.1中擦除它是不合时宜的。

似乎在复制课程,其余的都是意外。这一变化是为了使Hash方法更加一致。

  

开发人员应该如何适应这种行为变化? (我们都希望与Rails团队完全一样吗?)

简单的答案是切换到hash.dup.delete_if以在所有版本中保留相同的行为。

或者,您可以覆盖子类中的Hash#reject以保留旧行为,但随后您的哈希子类将破坏新的Hash#reject接口。

IMO开发者犯了一个错误。 Hash#reject的行为是可取的。方法不应该对自己的硬编码类名进行方法调用。方法应努力保留其调用者的类别。否则,你会遇到这样的情况:子类必须围绕所有内容编写包装器,以避免意外地将父对象返回给毫无戒心的用户。

如果它是isa或hasa关系并不重要。这是一个内部实现问题,对象的外部用户应该是不可见的。

如果需要一致性,Hash#select的行为应该已更改为匹配。