在 Ruby 中,相等运算符的顺序重要吗?

Is the order of the equality operator important in Ruby?

提问人:schande 提问时间:2/15/2021 更新时间:2/15/2021 访问量:92

问:

我已经在我的 Ruby 程序中使用了 bcrypt 库。我注意到相等运算符的顺序似乎很重要。根据“==”的左边或右边的变量,我会得到不同的结果。 下面是一个示例程序:

require 'bcrypt'
my_pw = "pw1"
puts "This is my unhashed password: #{my_pw}"
hashed_pw = BCrypt::Password.create(my_pw)
puts "This is my hashed password: #{hashed_pw}"

20.times{print"-"}
puts

puts "my_pw == hashed_pw equals:"
if (my_pw == hashed_pw)
  puts "TRUE"
else
  puts "FALSE"
end

puts "hashed_pw == my_pw equals:"
if (hashed_pw == my_pw)
  puts "TRUE"
else
  puts "FALSE"
end

问候 尚德

Ruby 运算符 bcrypt 相等 性 commutativity

评论

0赞 Stefan 2/15/2021
==就像大多数 Ruby 运算符解析为方法调用一样,因此行为取决于其接收者

答:

0赞 user1934428 2/15/2021 #1

例如,如果两个操作数都是 String 类型,则表达式是等效的。在您的例子中,一个操作数是 a,另一个是 .因此,调用 String 类中定义的相等方法,同时调用 BCrypt::P assword 中定义的相等方法。StringBCrypt::Passwordmy_pw == hashed_pwhashed_pw == my_pw

我从未使用过 BCrypt::P assword,但希望您能得到前者和后者。falsetrue

评论

0赞 Jörg W Mittag 2/15/2021
“例如,如果两个操作数都是 String 类型,则表达式是等价的”——如果你仔细观察,问题就更微妙了。 实际上, because 继承自 的实例。hashed_pwStringBCrypt::PasswordString
0赞 user1934428 2/15/2021
@JörgWMittag : 你是对的。这里的命名法很棘手。对于“x 是 Y 类型”,我的意思是这里而不是.这是我这边草率的演讲:我应该说这些课程是不一样的。主要论点仍然有效,即密码类定义了自己的相等运算符,这就是这里的相等不是可交换的原因。x.class == Yx.is_a?(Y)
0赞 user1934428 2/15/2021
@JärgWMittag : 实际上,Password 类派生自 String 这一事实(如此处所示)让我感到惊讶。我想知道哪些方法可以合理地用于对象?我(希望)无法在密码字符串中搜索子字符串。StringPassword
0赞 Amadan 2/16/2021
@user1934428 你可以,虽然内容是哈希,而不是原始密码,所以这个搜索的结果可能不是潜在的黑帽所期望的。例如,给定 ,则 是 ,但 是 。唯一的新方法是 和 。Passwordp = BCrypt::Password.create("my pretty password")p.include?("$12$")truep.include?("pretty")false::create#==
1赞 Amadan 2/16/2021
我主要是回应“我(希望)无法在密码字符串中搜索子字符串”。但是 bcrypt 哈希是一个字符串(尽管类的名称可能有点误导),无论字符串方法是否可以合理使用。
0赞 ndnenkov 2/15/2021 #2

在 Ruby 中,您可以覆盖给定类或实例的相等方法:

class Test
  define_method(:==) do |_other|
    true
  end
end

Test.new == 'foo' # => true
Test.new == nil # => true
Test.new == 42 # => true

'foo' == Test.new # => false
nil == Test.new # => false
42 == Test.new # => true

一般来说,在不使其对称的情况下凌驾于平等之上被认为是不好的做法,但有时您会在野外看到它。

评论

0赞 Stefan 2/15/2021
您可以通过以下方式定义它def ==(other)
4赞 spickermann 2/15/2021 #3

是的,有区别。

my_pw == hashed_pw调用字符串上的方法并作为参数传递。这意味着您正在使用该方法。来自 String#== 的文档==my_pwhashed_pwString#==

string == object → true or false

如果具有相同的长度和内容,则返回;如; 否则trueobjectselffalse

while 在 的实例上调用方法并作为参数传递。来自 BCrypt::P assword#== 的文档:hashed_pw == my_pw==BCrypt::Passwordmy_pw

#==(secret) ⇒ Object

将潜在机密与哈希值进行比较。如果密钥是原始密钥,则返回,否则返回。truefalse

1赞 Jörg W Mittag 2/15/2021 #4

这与平等没有任何关系。这只是面向对象编程的基础知识。

在 OOP 中,所有计算都是由对象向其他对象发送消息来完成的。OOP 的一个基本属性是接收方对象,并且只有接收方对象决定如何响应此消息。这就是在 OOP 中实现封装、数据隐藏和抽象的方式。

因此,如果您将消息发送到作为参数传递的对象,则可以决定如何解释此消息。如果将消息发送到作为参数传递的对象,则由它来决定如何解释此消息。没有内置机制可以保证这一点,并以相同的方式解释此消息。只有当两个对象决定相互协调时,响应才会真正相同。mabambabab

如果你仔细想想,如果结果相同,那就太奇怪了。2 - 33 - 2

这正是这里发生的事情:在第一个示例中,您将消息发送到 ,作为参数传递。 是 String 的实例,因此消息将被调度到 String#== 方法。此方法知道如何将接收对象与另一个对象进行比较。但是,它不知道如何将接收器与 BCrypt::P assword 进行比较,这就是 的类。==my_pwhashed_pwmy_pw==Stringhashed_pw

如果你仔细想想,这是有道理的:如果是 Ruby 之外的第三方类,一个内置的 Ruby 类怎么可能知道在实现该类时甚至不存在的东西?BCrypt::PasswordString

另一方面,在第二个示例中,您将消息发送到 ,作为参数传递。此消息被调度到 BCrypt::P assword#== 方法,该方法知道如何将接收方与 :==hashed_pwmy_pwString

方法:BCrypt::Password#==

定义于:lib/bcrypt/password.rb

#==(secret)
也称为:
Objectis_password?

将潜在机密与哈希值进行比较。如果密钥是原始密钥,则返回,否则返回。truefalse

实际上,在这种特殊情况下,问题比最初看起来要微妙得多。

我在上面写过,不知道如何处理 a 作为参数,因为它只知道如何比较 s。嗯,其实继承自,意思是IS-A,所以应该知道如何处理它!String#==BCrypt::PasswordStringBCrypt::PasswordStringBCrypt::PasswordStringString#==

但是想想有什么作用:String#==

string == object→ 或truefalse

如果具有相同的长度和内容,则返回;如; 否则 [...]trueobjectselffalse

想想看:“如果长度和内容相同,则返回”。对于哈希来说,这实际上永远不会是真的。 will be something like 和 will be something like,所以很明显,它们既不是相同的长度,也不是相同的内容。并且不可能是相同的内容,因为加密安全密码哈希的全部意义在于您无法反转它。因此,即使以某种方式想将自己呈现为原始密码,它也无法做到。trueobjectself'P@$$w0rd!'object'$2y$12$bxWYpd83lWyIr.dF62eO7.gp4ldf2hMxDofXPwdDZsnc2bCE7hN9q'objectobject

唯一可行的方法是,如果并且能够以某种方式“共同努力”来弄清楚平等。StringBCrypt::Password

现在,如果我们仔细查看 的文档,实际上有一种方法可以使它工作:String#==

如果不是 String 的实例,但响应 ,则使用 比较这两个字符串。objectto_strobject.==

因此,如果作者做出了不同的设计决定,那么它将起作用:BCrypt::Password

  1. 不要让继承自 。BCrypt::PasswordString
  2. 实现。这实际上允许与 互换使用,因为任何接受 s 的方法也应该接受响应 的对象。BCrypt::Password#to_strBCrypt::PasswordStringStringto_str

现在,根据 的文档,如果您编写 ,则会发生以下情况:String#==my_pw == hashed_pw

  1. String#==注意不是 .hashed_pwString
  2. String#==通知,确实响应 。hashed_pwto_str
  3. 因此,它将发送消息(在我们的例子中相当于 ),这意味着我们现在处于您问题的第二个场景中,效果很好。object == selfhashed_pw == my_pw

下面是一个如何工作的示例:

class Pwd
  def initialize(s)
    @s = s.downcase.freeze
  end

  def to_str
    p __callee__
    @s.dup.freeze
  end

  def ==(other)
    p __callee__, other
    @s == other.downcase
  end

end

p = Pwd.new('HELLO')
s = 'hello'

p == s
# :==
# "hello"
#=> true

s == p
# :==
# "hello"
#=> true

正如你所看到的,我们得到了我们期望的结果,并且两次都被调用。此外,它永远不会被调用,它只会被 检查。Pwd#==to_strString#==

所以,事实证明,具有讽刺意味的是,问题不在于它不知道如何处理对象,而在于它确实知道如何将它们作为通用字符串来处理。如果他们不是,而只是回应,那么实际上会知道向他们寻求帮助。String#==BCrypt::PasswordStringto_strString#==

Ruby 中的数值对象有一个完整的强制协议,以确保不同“类数”操作数类型之间的算术运算,即使是第三方数值库也是如此。