Ruby Set不同类型对象的成员资格

时间:2017-04-05 13:29:45

标签: ruby set typing

Ruby不是一种打字语言,因此我觉得这很奇怪。例如:

class Test
    attr_reader :field

    def initialize(f)
        @field = String(f)
    end

    def ==(other)
        field == String(other)
    end

    def eql?(other)
        field.eql? String(other)
    end

    def hash
        field.hash
    end

    def to_s
        field
    end
end

s = Set.new([Test.new('string')])
# => [#<Test:0x007fc97da17ea0 @field="string">]

puts Test.new('string').eql?(Test.new('string'))
# true

puts Test.new('string').hash == Test.new('string').hash
# true

puts s.member? Test.new('string')
# true

puts s.member? Test.new('other')
# false

puts Test.new('string') == 'string'
# true

puts Test.new('string').eql? 'string'
# true

puts Test.new('string').hash == 'string'.hash
# true

但是,

puts s.member? 'string'
# false

似乎Ruby正在内部强制执行某些类型检查。应该是这样吗?

1 个答案:

答案 0 :(得分:3)

您的主要问题是String#eql?

puts Test.new('string').eql? 'string'
# true
puts 'string'.eql? Test.new('string')
# false

你是对的,似乎有一些内部类型检查:

rb_str_eql(VALUE str1, VALUE str2)
{
    if (str1 == str2) return Qtrue;
    if (!RB_TYPE_P(str2, T_STRING)) return Qfalse;
    return str_eql(str1, str2);
}

请注意,您的示例在反过来使用时有效:

s = Set.new(['string'])
puts s.member? Test.new('string')
# true

如果你真的想要实现这种行为,你可以修补String#eql?

module TestStringEquality
  def eql?(other)
    other.is_a?(Test) ? other.eql?(self) : super
  end
end

s = Set.new([Test.new('string')])
puts s.member? 'string'
# false

class String
  prepend TestStringEquality
end

puts s.member? 'string'
# true

注意,这种方法可能用在每个宝石中。

最后,请记住Set是纯Ruby实现,Hash作为后端。这可能会使谷歌更容易获取信息和文档。