在从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 devs,discussed a little bit,并在this blog post中详细说明。这种变化也打破了Rails&#39; HashWithIndifferentAccess
根据this issue,a pull request for Rails 4发布了{{3}}(但在Rails 3.2.22中仍未解决?)
显然,这种行为让很多人措手不及,考虑到它如何打破已知的Ruby宝石世界(包括Rails,Hashie等)依赖于对象不应该出乎意料的基本观点,这种变化听起来很荒谬改变类型。
对于一些知情的Ruby开发者来说,我的问题是:
Hash#reject
将始终返回Hash
? (与调用此函数的类的实例相反?例如1.9.3 C源return rb_hash_delete_if(rb_obj_dup(hash));
)#reject
,打破合理的期望,使用这些Enumerable
函数仍会返回相同类型的对象?答案 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 #9223有Matz 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
的行为应该已更改为匹配。