是否可以覆盖 class 或模块之外的内置 Ruby 方法?
Is it possible to override a built-in Ruby method outside of a class or module?
我正在研究如何 fiddle 在 Ruby 中使用特殊的排序机制。我最终在 Ruby:
中重写了 this neat JavaScript solution
class SpecialStr
include Comparable
attr_accessor :str
def initialize (str)
@str = str
end
def <=> (other)
self_num, self_string = @str.split(' ')
other_num, other_string = other.str.split(' ')
self_num > other_num ? 1 : other_num > self_num ? -1 :
self_string > other_string ? -1 : 1
end
end
arr = ['2 xxx', '20 axxx', '2 m', '38 xxxx', '20 bx', '8540 xxxxxx', '2 z']
arr_object = []
arr.each { |str| arr_object << SpecialStr.new(str) }
arr_object.sort! { |x, y| y <=> x }
output_arr = []
arr_object.each { |obj| output_arr << obj.str}
puts output_arr
这具有所需的输出(数字降序,然后字符串升序):
8540 xxxxxx
38 xxxx
20 axxx
20 bx
2 m
2 xxx
2 z
但代码似乎不必要地复杂。 (Ruby 应该比 JS 更简洁!)所以我问自己(现在我问你),为什么我不能这样做?
def <=> (other)
self_num, self_string = self.split(' ')
other_num, other_string = other.split(' ')
self_num > other_num ? 1 : other_num > self_num ? -1 :
self_string > other_string ? -1 : 1
end
arr = ['2 xxx', '20 axxx', '2 m', '38 xxxx', '20 bx', '8540 xxxxxx', '2 z']
arr.sort! { |x, y| y <=> x }
puts arr
这输出不正确,基于 sort
好像我没有重新定义 <=>
:
8540 xxxxxx
38 xxxx
20 bx
20 axxx
2 z
2 xxx
2 m
此处的代码较短,但不起作用。它使用内置于 Ruby 的 Comparable
模块中的 <=>
版本,而不是我尝试覆盖它。为什么我不能覆盖它?只能在 类 或模块内部重写方法吗? 是否有更短的方法来编写 Ruby 中的第一个脚本? (对不起,如果这是一个菜鸟问题,我是初学者。)
写的时候
arr.sort! { |x, y| y <=> x }
相当于
arr.sort! { |x, y| y.<=>(x) }
即它正在调用 y
版本的 <=>
(宇宙飞船)运算符。由于 y
只是一个 String
,因此它执行字符串的默认比较。
为了更简洁地编写代码,您可以在传递给 sort!
:
的块中编写自定义比较逻辑
arr.sort! do |x, y|
x_num, x_string = x.split(' ')
y_num, y_string = y.split(' ')
y_num > x_num ? 1 : x_num > y_num ? -1 :
y_string > x_string ? -1 : 1
end
或者,将其编写为独立方法:
def my_compare(x, y)
x_num, x_string = x.split(' ')
y_num, y_string = y.split(' ')
y_num > x_num ? 1 : x_num > y_num ? -1 :
y_string > x_string ? -1 : 1
end
并从 sort!
调用它:
arr.sort! { |x, y| my_compare(x, y) }
一些可能有助于澄清的事情:
在 Ruby 中,没有 free-floating 方法(即未附加到 class 或模块的方法)。当您在任何 class 或模块之外编写 def ...
时,该方法将作为实例方法添加到 Object
。 严格来说,有unbound methods,但即使是这些也需要与对象关联才能被调用。
接下来要记住的是 <=>
的默认实现来自哪里:它在 Kernel
模块中,它包含在 class Object
中。
因此,当您在 class 之外编写 def <=>(other)
... 时,您将覆盖 Object
:
的方法
[1] pry(main)> method(:<=>).owner
=> Kernel
[2] pry(main)> def <=>(other)
[2] pry(main)* puts "overridden!"
[2] pry(main)* end
=> :<=>
[3] pry(main)> method(:<=>).owner
=> Object
但是,String
class 会覆盖 <=>
本身。为了将字符串与另一个对象进行比较,String
的实现将优先于 Object
、 中的实现使用,即使 您已经覆盖了 Object
.
但是,如果您的 class 没有自己的 <=>
(或者它与 class 层次结构中的 Object
之间的重写实现)那么你在 Object
上重写的方法确实会被使用:
[6] pry(main)> class Something; end
=> nil
[7] pry(main)> s1 = Something.new
=> #<Something:0x007fddb4431ba8>
[8] pry(main)> s2 = Something.new
=> #<Something:0x007fddb4469760>
[9] pry(main)> s1 <=> s2
overridden!
=> nil
pry
中演示内容的解释
第一个片段是使用 method
方法获取一个方法,然后使用 owner
找出在 class 层次结构中定义该方法的位置。
再举个例子:
class Animal
def eat
puts "Munch!"
end
end
class Dog < Animal
def bark
puts "yap!"
end
end
所以如果我们有一只狗:
buddy = Dog.new
我们可以找出它的方法来自哪里:
[10] pry(main)> buddy.method(:eat).owner
=> Animal
[11] pry(main)> buddy.method(:bark).owner
=> Dog
所以在原始示例中我们可以看到 <=>
开始引用 Kernel
模块中的方法,但是当我们这样做时 def <=>
... 这添加了一个方法直接到 Object
现在覆盖了包含的方法。
第二个示例展示了当最小 class 没有自己实现 <=>
时会发生什么。 instance_methods(false)
可以向我们展示直接在 class 上实现的实例方法。空的 Something
class 没有任何 :)
[14] pry(main)> Something.instance_methods(false)
=> []
因此它将使用继承的 <=>
方法。
你的问题是:
y <=> x
只是一种花哨的(和human-friendly)的写法:
y.<=>(x)
所以 <=>
运算符不是函数调用,它是运算符左侧的 方法 调用。该方法调用不会使用您的 def <=>
因为您的比较器方法未在您正在排序的数组中的对象上定义,您已经在其他一些 [=69= 上创建了 <=>
方法].
在JavaScript中,你是这样说的:
a.sort(function(a, b) { ... })
或更近代:
a.sort((a, b) => ...)
所以你要交给 sort
一个使用比较器的函数,你没有在任何地方定义比较器运算符,只是一个接受两个参数和 returns 所需值的函数。
在Ruby中,您通常使用块作为"callbacks":
arr.sort! do |a, b|
a_num, a_string = a.split(' ')
b_num, b_string = b.split(' ')
a_num > b_num ? 1 : b_num > a_num ? -1 : a_string > b_string ? -1 : 1
end
在我们继续之前,您的比较器逻辑有问题,因为 Enumerable#sort
的块应该
return -1, 0, or +1 depending on the comparison between a
and b
.
并且您的块不处理 0
(相等)情况。此外,您的 _num
仍然是字符串,因此它们不会像数字一样进行比较。第一个问题可以通过使用 Array#<=>
(逐个元素比较数组)来解决,然后第二个问题可以通过简单的 to_i
调用来解决:
arr.sort! do |a, b|
a_num, a_string = a.split(' ')
b_num, b_string = b.split(' ')
[a_num.to_i, a_string] <=> [b_num.to_i, b_string]
end
您可以更进一步切换到 sort_by!
:
arr.sort_by! do |e|
i, s = e.split(' ')
[i.to_i, s]
end
如果你想在多个地方使用块的逻辑,你可以使用 lambda 更接近 JavaScript 版本:
cmp = ->(a, b) do
a_num, a_string = a.split(' ')
b_num, b_string = b.split(' ')
[a_num.to_i, a_string] <=> [b_num.to_i, b_string]
end
arr1.sort!(&cmp)
arr2.sort!(&cmp)
natural = ->(e) do
i, s = e.split(' ')
[i.to_i, s]
end
arr1.sort_by!(&natural)
arr2.sort_by!(&natural)
或单独的方法:
def cmp(a, b)
a_num, a_string = a.split(' ')
b_num, b_string = b.split(' ')
[a_num.to_i, a_string] <=> [b_num.to_i, b_string]
end
def some_other_method
arr1.sort!(&method(:cmp))
arr2.sort!(&method(:cmp))
end
def natural(e)
i, s = e.split(' ')
[i.to_i, s]
end
def some_other_other_method
arr1.sort_by!(&method(:natural))
arr2.sort_by!(&method(:natural))
end
如果您真的想将 self_number
和 other_number
值作为字符串进行比较,则省去 to_i
调用并进一步简化 blocks/lambdas:
arr.sort! { |a, b| a.split(' ') <=> b.split(' ') }
arr.sort_by! { |e| e.split(' ') }
最简单的方法是将字符串拆分为数字和单词,然后按负数数组(以获得递减的数字)和单词排序:
arr = ['2 xxx', '20 axxx', '2 m', '38 xxxx', '20 bx', '8540 xxxxxx', '2 z']
arr.sort_by! do |number_word|
number, word = number_word.split
[ -number.to_i, word ]
end
puts arr
# =>
# 8540 xxxxxx
# 38 xxxx
# 20 axxx
# 20 bx
# 2 m
# 2 xxx
# 2 z
数组排序时,第一个元素(-number
)优先。如果两个第一个元素相同,则排序使用第二个元素 (word
).
我正在研究如何 fiddle 在 Ruby 中使用特殊的排序机制。我最终在 Ruby:
中重写了 this neat JavaScript solutionclass SpecialStr
include Comparable
attr_accessor :str
def initialize (str)
@str = str
end
def <=> (other)
self_num, self_string = @str.split(' ')
other_num, other_string = other.str.split(' ')
self_num > other_num ? 1 : other_num > self_num ? -1 :
self_string > other_string ? -1 : 1
end
end
arr = ['2 xxx', '20 axxx', '2 m', '38 xxxx', '20 bx', '8540 xxxxxx', '2 z']
arr_object = []
arr.each { |str| arr_object << SpecialStr.new(str) }
arr_object.sort! { |x, y| y <=> x }
output_arr = []
arr_object.each { |obj| output_arr << obj.str}
puts output_arr
这具有所需的输出(数字降序,然后字符串升序):
8540 xxxxxx
38 xxxx
20 axxx
20 bx
2 m
2 xxx
2 z
但代码似乎不必要地复杂。 (Ruby 应该比 JS 更简洁!)所以我问自己(现在我问你),为什么我不能这样做?
def <=> (other)
self_num, self_string = self.split(' ')
other_num, other_string = other.split(' ')
self_num > other_num ? 1 : other_num > self_num ? -1 :
self_string > other_string ? -1 : 1
end
arr = ['2 xxx', '20 axxx', '2 m', '38 xxxx', '20 bx', '8540 xxxxxx', '2 z']
arr.sort! { |x, y| y <=> x }
puts arr
这输出不正确,基于 sort
好像我没有重新定义 <=>
:
8540 xxxxxx
38 xxxx
20 bx
20 axxx
2 z
2 xxx
2 m
此处的代码较短,但不起作用。它使用内置于 Ruby 的 Comparable
模块中的 <=>
版本,而不是我尝试覆盖它。为什么我不能覆盖它?只能在 类 或模块内部重写方法吗? 是否有更短的方法来编写 Ruby 中的第一个脚本? (对不起,如果这是一个菜鸟问题,我是初学者。)
写的时候
arr.sort! { |x, y| y <=> x }
相当于
arr.sort! { |x, y| y.<=>(x) }
即它正在调用 y
版本的 <=>
(宇宙飞船)运算符。由于 y
只是一个 String
,因此它执行字符串的默认比较。
为了更简洁地编写代码,您可以在传递给 sort!
:
arr.sort! do |x, y|
x_num, x_string = x.split(' ')
y_num, y_string = y.split(' ')
y_num > x_num ? 1 : x_num > y_num ? -1 :
y_string > x_string ? -1 : 1
end
或者,将其编写为独立方法:
def my_compare(x, y)
x_num, x_string = x.split(' ')
y_num, y_string = y.split(' ')
y_num > x_num ? 1 : x_num > y_num ? -1 :
y_string > x_string ? -1 : 1
end
并从 sort!
调用它:
arr.sort! { |x, y| my_compare(x, y) }
一些可能有助于澄清的事情:
在 Ruby 中,没有 free-floating 方法(即未附加到 class 或模块的方法)。当您在任何 class 或模块之外编写 def ...
时,该方法将作为实例方法添加到 Object
。 严格来说,有unbound methods,但即使是这些也需要与对象关联才能被调用。
接下来要记住的是 <=>
的默认实现来自哪里:它在 Kernel
模块中,它包含在 class Object
中。
因此,当您在 class 之外编写 def <=>(other)
... 时,您将覆盖 Object
:
[1] pry(main)> method(:<=>).owner
=> Kernel
[2] pry(main)> def <=>(other)
[2] pry(main)* puts "overridden!"
[2] pry(main)* end
=> :<=>
[3] pry(main)> method(:<=>).owner
=> Object
但是,String
class 会覆盖 <=>
本身。为了将字符串与另一个对象进行比较,String
的实现将优先于 Object
、 中的实现使用,即使 您已经覆盖了 Object
.
但是,如果您的 class 没有自己的 <=>
(或者它与 class 层次结构中的 Object
之间的重写实现)那么你在 Object
上重写的方法确实会被使用:
[6] pry(main)> class Something; end
=> nil
[7] pry(main)> s1 = Something.new
=> #<Something:0x007fddb4431ba8>
[8] pry(main)> s2 = Something.new
=> #<Something:0x007fddb4469760>
[9] pry(main)> s1 <=> s2
overridden!
=> nil
pry
第一个片段是使用 method
方法获取一个方法,然后使用 owner
找出在 class 层次结构中定义该方法的位置。
再举个例子:
class Animal
def eat
puts "Munch!"
end
end
class Dog < Animal
def bark
puts "yap!"
end
end
所以如果我们有一只狗:
buddy = Dog.new
我们可以找出它的方法来自哪里:
[10] pry(main)> buddy.method(:eat).owner
=> Animal
[11] pry(main)> buddy.method(:bark).owner
=> Dog
所以在原始示例中我们可以看到 <=>
开始引用 Kernel
模块中的方法,但是当我们这样做时 def <=>
... 这添加了一个方法直接到 Object
现在覆盖了包含的方法。
第二个示例展示了当最小 class 没有自己实现 <=>
时会发生什么。 instance_methods(false)
可以向我们展示直接在 class 上实现的实例方法。空的 Something
class 没有任何 :)
[14] pry(main)> Something.instance_methods(false)
=> []
因此它将使用继承的 <=>
方法。
你的问题是:
y <=> x
只是一种花哨的(和human-friendly)的写法:
y.<=>(x)
所以 <=>
运算符不是函数调用,它是运算符左侧的 方法 调用。该方法调用不会使用您的 def <=>
因为您的比较器方法未在您正在排序的数组中的对象上定义,您已经在其他一些 [=69= 上创建了 <=>
方法].
在JavaScript中,你是这样说的:
a.sort(function(a, b) { ... })
或更近代:
a.sort((a, b) => ...)
所以你要交给 sort
一个使用比较器的函数,你没有在任何地方定义比较器运算符,只是一个接受两个参数和 returns 所需值的函数。
在Ruby中,您通常使用块作为"callbacks":
arr.sort! do |a, b|
a_num, a_string = a.split(' ')
b_num, b_string = b.split(' ')
a_num > b_num ? 1 : b_num > a_num ? -1 : a_string > b_string ? -1 : 1
end
在我们继续之前,您的比较器逻辑有问题,因为 Enumerable#sort
的块应该
return -1, 0, or +1 depending on the comparison between
a
andb
.
并且您的块不处理 0
(相等)情况。此外,您的 _num
仍然是字符串,因此它们不会像数字一样进行比较。第一个问题可以通过使用 Array#<=>
(逐个元素比较数组)来解决,然后第二个问题可以通过简单的 to_i
调用来解决:
arr.sort! do |a, b|
a_num, a_string = a.split(' ')
b_num, b_string = b.split(' ')
[a_num.to_i, a_string] <=> [b_num.to_i, b_string]
end
您可以更进一步切换到 sort_by!
:
arr.sort_by! do |e|
i, s = e.split(' ')
[i.to_i, s]
end
如果你想在多个地方使用块的逻辑,你可以使用 lambda 更接近 JavaScript 版本:
cmp = ->(a, b) do
a_num, a_string = a.split(' ')
b_num, b_string = b.split(' ')
[a_num.to_i, a_string] <=> [b_num.to_i, b_string]
end
arr1.sort!(&cmp)
arr2.sort!(&cmp)
natural = ->(e) do
i, s = e.split(' ')
[i.to_i, s]
end
arr1.sort_by!(&natural)
arr2.sort_by!(&natural)
或单独的方法:
def cmp(a, b)
a_num, a_string = a.split(' ')
b_num, b_string = b.split(' ')
[a_num.to_i, a_string] <=> [b_num.to_i, b_string]
end
def some_other_method
arr1.sort!(&method(:cmp))
arr2.sort!(&method(:cmp))
end
def natural(e)
i, s = e.split(' ')
[i.to_i, s]
end
def some_other_other_method
arr1.sort_by!(&method(:natural))
arr2.sort_by!(&method(:natural))
end
如果您真的想将 self_number
和 other_number
值作为字符串进行比较,则省去 to_i
调用并进一步简化 blocks/lambdas:
arr.sort! { |a, b| a.split(' ') <=> b.split(' ') }
arr.sort_by! { |e| e.split(' ') }
最简单的方法是将字符串拆分为数字和单词,然后按负数数组(以获得递减的数字)和单词排序:
arr = ['2 xxx', '20 axxx', '2 m', '38 xxxx', '20 bx', '8540 xxxxxx', '2 z']
arr.sort_by! do |number_word|
number, word = number_word.split
[ -number.to_i, word ]
end
puts arr
# =>
# 8540 xxxxxx
# 38 xxxx
# 20 axxx
# 20 bx
# 2 m
# 2 xxx
# 2 z
数组排序时,第一个元素(-number
)优先。如果两个第一个元素相同,则排序使用第二个元素 (word
).