Ruby 中相等运算符的顺序重要吗?
Is the order of the equality operator important in Ruby?
我在我的 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
问候
尚德
例如,如果两个操作数都是字符串类型,则表达式将是等价的。在您的例子中,一个操作数是 String
,另一个是 BCrypt::Password
。因此 my_pw == hashed_pw
调用在字符串 class 中定义的相等方法,而 hashed_pw == my_pw
调用在 BCrypt::Password.
中定义的方法
我从未与 BCrypt::Password 合作过,但希望前者得到 false
,后者得到 true
。
在 Ruby 中,您可以覆盖给定 class 或实例的相等方法:
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
一般来说,在不对称的情况下覆盖相等性被认为是不好的做法,但您有时会在野外看到它。
是的,有区别。
my_pw == hashed_pw
在 my_pw
字符串上调用 ==
方法并将 hashed_pw
作为参数传递。这意味着您正在使用 String#==
方法。来自 docs of String#==
:
string == object → true or false
Returns true
if object
has the same length and content; as self
; false
otherwise
而 hashed_pw == my_pw
在 BCrypt::Password
的实例上调用 ==
方法并将 my_pw
作为参数传递。来自 BCrypt::Password#==
的文档:
#==(secret) ⇒ Object
Compares a potential secret against the hash. Returns true
if the secret is the original secret, false
otherwise.
这与平等没有任何关系。这只是面向对象编程的基础。
在 OOP 中,所有计算都是通过对象向其他对象发送消息来完成的。 OOP 的基本属性 之一是接收者对象并且只有接收者对象 决定如何响应此消息。 OOP中的封装、数据隐藏和抽象就是这样实现的。
因此,如果您将消息 m
发送到对象 a
并传递 b
作为参数,那么 a
将决定如何解释此消息。如果您将消息 m
发送到对象 b
并传递 a
作为参数,那么 b
将决定如何解释此消息。没有内置机制可以保证 a
和 b
对这条消息的解释相同。 只有如果两个对象决定相互协调,响应实际上是否相同。
仔细想想,如果2 - 3
和3 - 2
的结果是一样的,那就很奇怪了
这正是这里发生的事情:在第一个示例中,您将消息 ==
发送到 my_pw
,并将 hashed_pw
作为参数传递。 my_pw
是 String
class, thus the ==
message will be dispatched to the String#==
method. This method knows how to compare the receiver object to another String
. It does, however, not know how to compare the receiver to a BCrypt::Password
的实例,这就是 hashed_pw
的 class。
如果您考虑一下,这是有道理的:BCrypt::Password
是来自 Ruby 之外的第三方 class, 怎么可能 内置 Ruby class 知道 在 String
class 时甚至不存在的东西实施了吗?
另一方面,在您的第二个示例中,您将消息 ==
发送到 hashed_pw
,并将 my_pw
作为参数传递。此消息被分派到 BCrypt::Password#==
方法, 知道如何将接收者与 String
:
进行比较
Method: BCrypt::Password#==
Defined in: lib/bcrypt/password.rb
#==(secret)
⇒ Object
Also known as: is_password?
Compares a potential secret against the hash. Returns true
if the secret is the original secret, false
otherwise.
实际上,这个特殊案例中的问题比乍看起来更微妙。
我在上面写到 String#==
不知道如何使用 BCrypt::Password
作为参数,因为它只知道如何比较 String
。好吧,其实BCrypt::Password
继承自String
,意思就是一个BCrypt::Password
IS-AString
,所以String#==
应该知道怎么处理吧!
但想想 String#==
的作用:
string == object
→ true
or false
Returns true
if object
has the same length and content; as self
; false
otherwise […]
想一想:“returns true
如果 object
具有相同的长度和内容”。对于哈希,这实际上永远不会是真的。 self
类似于 'P@$$w0rd!'
而 object
类似于 'y$bxWYpd83lWyIr.dF62eO7.gp4ldf2hMxDofXPwdDZsnc2bCE7hN9q'
,很明显,它们的长度和内容都不相同。而且 object
不可能是 相同的内容,因为加密安全密码哈希的全部要点是你 不能 反转它。所以,即使 object
想以某种方式将自己显示为原始密码,它也做不到。
唯一可行的方法是,如果 String
和 BCrypt::Password
能以某种方式“共同努力”找出相等性。
现在,如果我们仔细查看 String#==
的文档,实际上有一种方法可以使它起作用:
If object
is not an instance of String but responds to to_str
, then the two strings are compared using object.==
.
因此,如果 BCrypt::Password
的作者做出了不同的设计决定,那么它将起作用:
- 不要让
BCrypt::Password
继承自 String
。
- 实施
BCrypt::Password#to_str
。这实际上允许 BCrypt::Password
与 String
实际互换使用,因为任何接受 String
s 的方法都应该 也 接受响应 to_str
.
现在,根据 String#==
的文档,如果您编写 my_pw == hashed_pw
,则会发生以下情况:
String#==
注意到 hashed_pw
不是 String
。
String#==
注意到 hashed_pw
确实 响应 to_str
。
- 因此,它将发送消息
object == self
(在我们的例子中等同于 hashed_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_str
永远不会被调用,它只会被 String#==
.
检查
所以,具有讽刺意味的是,问题不在于 String#==
不知道如何处理 BCrypt::Password
对象,而实际上问题在于它 知道如何处理它们 作为通用 String
s。如果他们不是 String
而只是回复了 to_str
,那么 String#==
实际上会知道向他们寻求帮助。
Numeric
objects in Ruby have a whole coercion protocol 以确保即使对于第三方数字库也支持不同“类数字”操作数类型之间的算术运算。
我在我的 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
问候 尚德
例如,如果两个操作数都是字符串类型,则表达式将是等价的。在您的例子中,一个操作数是 String
,另一个是 BCrypt::Password
。因此 my_pw == hashed_pw
调用在字符串 class 中定义的相等方法,而 hashed_pw == my_pw
调用在 BCrypt::Password.
我从未与 BCrypt::Password 合作过,但希望前者得到 false
,后者得到 true
。
在 Ruby 中,您可以覆盖给定 class 或实例的相等方法:
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
一般来说,在不对称的情况下覆盖相等性被认为是不好的做法,但您有时会在野外看到它。
是的,有区别。
my_pw == hashed_pw
在 my_pw
字符串上调用 ==
方法并将 hashed_pw
作为参数传递。这意味着您正在使用 String#==
方法。来自 docs of String#==
:
string == object → true or false
Returns
true
ifobject
has the same length and content; asself
;false
otherwise
而 hashed_pw == my_pw
在 BCrypt::Password
的实例上调用 ==
方法并将 my_pw
作为参数传递。来自 BCrypt::Password#==
的文档:
#==(secret) ⇒ Object
Compares a potential secret against the hash. Returns
true
if the secret is the original secret,false
otherwise.
这与平等没有任何关系。这只是面向对象编程的基础。
在 OOP 中,所有计算都是通过对象向其他对象发送消息来完成的。 OOP 的基本属性 之一是接收者对象并且只有接收者对象 决定如何响应此消息。 OOP中的封装、数据隐藏和抽象就是这样实现的。
因此,如果您将消息 m
发送到对象 a
并传递 b
作为参数,那么 a
将决定如何解释此消息。如果您将消息 m
发送到对象 b
并传递 a
作为参数,那么 b
将决定如何解释此消息。没有内置机制可以保证 a
和 b
对这条消息的解释相同。 只有如果两个对象决定相互协调,响应实际上是否相同。
仔细想想,如果2 - 3
和3 - 2
的结果是一样的,那就很奇怪了
这正是这里发生的事情:在第一个示例中,您将消息 ==
发送到 my_pw
,并将 hashed_pw
作为参数传递。 my_pw
是 String
class, thus the ==
message will be dispatched to the String#==
method. This method knows how to compare the receiver object to another String
. It does, however, not know how to compare the receiver to a BCrypt::Password
的实例,这就是 hashed_pw
的 class。
如果您考虑一下,这是有道理的:BCrypt::Password
是来自 Ruby 之外的第三方 class, 怎么可能 内置 Ruby class 知道 在 String
class 时甚至不存在的东西实施了吗?
另一方面,在您的第二个示例中,您将消息 ==
发送到 hashed_pw
,并将 my_pw
作为参数传递。此消息被分派到 BCrypt::Password#==
方法, 知道如何将接收者与 String
:
Method:
BCrypt::Password#==
Defined in: lib/bcrypt/password.rb
#==(secret)
⇒Object
Also known as:is_password?
Compares a potential secret against the hash. Returns
true
if the secret is the original secret,false
otherwise.
实际上,这个特殊案例中的问题比乍看起来更微妙。
我在上面写到 String#==
不知道如何使用 BCrypt::Password
作为参数,因为它只知道如何比较 String
。好吧,其实BCrypt::Password
继承自String
,意思就是一个BCrypt::Password
IS-AString
,所以String#==
应该知道怎么处理吧!
但想想 String#==
的作用:
string == object
→true
orfalse
Returns
true
ifobject
has the same length and content; asself
;false
otherwise […]
想一想:“returns true
如果 object
具有相同的长度和内容”。对于哈希,这实际上永远不会是真的。 self
类似于 'P@$$w0rd!'
而 object
类似于 'y$bxWYpd83lWyIr.dF62eO7.gp4ldf2hMxDofXPwdDZsnc2bCE7hN9q'
,很明显,它们的长度和内容都不相同。而且 object
不可能是 相同的内容,因为加密安全密码哈希的全部要点是你 不能 反转它。所以,即使 object
想以某种方式将自己显示为原始密码,它也做不到。
唯一可行的方法是,如果 String
和 BCrypt::Password
能以某种方式“共同努力”找出相等性。
现在,如果我们仔细查看 String#==
的文档,实际上有一种方法可以使它起作用:
If
object
is not an instance of String but responds toto_str
, then the two strings are compared usingobject.==
.
因此,如果 BCrypt::Password
的作者做出了不同的设计决定,那么它将起作用:
- 不要让
BCrypt::Password
继承自String
。 - 实施
BCrypt::Password#to_str
。这实际上允许BCrypt::Password
与String
实际互换使用,因为任何接受String
s 的方法都应该 也 接受响应to_str
.
现在,根据 String#==
的文档,如果您编写 my_pw == hashed_pw
,则会发生以下情况:
String#==
注意到hashed_pw
不是String
。String#==
注意到hashed_pw
确实 响应to_str
。- 因此,它将发送消息
object == self
(在我们的例子中等同于hashed_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_str
永远不会被调用,它只会被 String#==
.
所以,具有讽刺意味的是,问题不在于 String#==
不知道如何处理 BCrypt::Password
对象,而实际上问题在于它 知道如何处理它们 作为通用 String
s。如果他们不是 String
而只是回复了 to_str
,那么 String#==
实际上会知道向他们寻求帮助。
Numeric
objects in Ruby have a whole coercion protocol 以确保即使对于第三方数字库也支持不同“类数字”操作数类型之间的算术运算。