猴子修补 * 运算符
monkey patching the * operator
我是 ruby 的新手,正在尝试了解猴子补丁。
所以在练习一个简单的 Vektor 计算器的旧项目时。
我写了下面的代码,它允许我将一个 Vektor 与一个数字和两个向量相乘:
class Vektor
attr_accessor :x #instead of getter and setter!-
def initialize (*vektors)
if vektors.length==0
@x=Array.new(3,0)
else
@x=vektors
end
end
def size
@x.length
end
def *(other)
case other
when Vektor
if (self.size != other.size)
return "the vektors don't have the same length!"
else
result=0
for i in 0 ... other.size
result += (self.x[i] * other.x[i])
end
return result
end
when Numeric
for i in 0 ... self.size
self.x[i]*=other
end
return self
end
end
end
这里是整数 class 所以我可以按照这样的示例以其他方式进行乘法 -> 5*vektor
class Integer
def mul(other)
for i in 0 ... other.size
other.x[i]*=self # this is the cause of the error!
end
return other
end
alias_method :* , :mul
end
obj1=Vektor.new(2,2,2)
puts 3*obj1
我的问题是出现错误,我不知道为什么,但我可以假设这是因为我在 alias_method
中使用了 *
在 mul
方法中使用。
输出如下:
undefined method `x' for 3:Integer (NoMethodError)
已编辑:
作业文本:
Extend the Integer class with a monkey patch so that you can write 5 *
vector instead of vector * 5.
Test whether this patch works via a suitable unit test.
Tip: You will want to use the alias keyword or the alias_method method
to achieve the goal.
我完全理解问题的原因,但我似乎找不到可以防止错误发生的解决方法!
这就是我目前正在做的事情,但工作方式不对:(
class Integer
def mul(other)
case (other)
when Numeric
return self*other
when Vektor
for i in 0 ... other.size
other.x[i]*=self # this is the cause of the error!
end
return other
end
end
alias_method :**,:mul
end
obj1=Vektor.new(2,2,2)
puts obj1*3
puts 3**Vektor.new(3,3,3)
请注意,此时我所关心的只是扩展我的知识,而不是得到一个愚蠢的笔记。 :)
other.x[i]*=self
与
相同
other.x[i] = other.x[i] * self
或者让它变得非常明确
other.x().[]=(i)(other.x().[](i).*(self))
other.x[i]
是一个 Integer
,self
也是一个 Integer
(在本例中是 3
)。
在你的Integer
方法中,你调用了other.x
,但是当你乘以some_integer * 3
,那么在你的Integer#*
方法中,other
是3
,所以你正在调用 3.x
,它确实不存在。
请注意,您的 Vektor#*
方法现在也已损坏,因为它也需要乘以两个 Integer
,但您只是覆盖了知道如何乘以的 Integer#*
方法将两个 Integer
相乘(实际上也知道如何将 Integer
与 任何正确实现 Numeric#coerce
协议的对象 相乘)与一个只知道如何乘以一个 Integer
和一个 Vektor
,因此不再知道如何乘以两个 Integer
。
另一个问题是,在您的 Vektor#*
方法中,您检查 other
是否是 Numeric
的一个实例,但反过来您只实现 [=22] 的乘法=]秒。这使您的乘法不对称,例如some_vektor * 1.0
可以,但 1.0 * some_vektor
不行。
正确的实现是完全不触及 Integer
,而只是实现数字强制协议。这将解决您所有的问题:您不必猴子修补 anything,您的 Vektor
s 将自动与 any[=155 一起工作=] Numeric
在核心库、标准库、Ruby 生态系统中,实际上什至还没有被编写出来。像这样:
class Vektor
def self.inherited(*)
raise TypeError, "#{self} is immutable and cannot be inherited from."
end
def initialize(*vektors)
self.x = if vektors.size.zero?
Array.new(3, 0)
else
vektors
end.freeze
end
singleton_class.alias_method :[], :new
alias_method :length, def size
x.size
end
def coerce(other)
[self, other]
end
def *(other)
case other
when Vektor
raise ArgumentError, "the vektors don't have the same length!" if size != other.size
x.zip(other.x).map {|a, b| a * b }.sum
when Numeric
self.class.new(*x.map(&other.method(:*)))
else
a, b = other.coerce(self)
a * b
end
end
protected
attr_reader :x # `x` should not be writeable by anybody!
private
attr_writer :x
freeze
end
您会注意到我对您的代码做了一些更改:
Vektor
现在是不可变的,Vektor#*
return 是一个新的 Vektor
而不是变异的 self
。尽可能保持对象不可变通常是个好主意,但它 尤其是 对于 "number-like" 对象(如 Vektor
s。如果 2 * 3
没有 return 6
而是让 2
具有值 6
,你会非常惊讶,不是吗?但这正是您的代码对 Vektor
s! 所做的
x
不再对所有人公开,最重要的是,不再对所有人 可写 。
- 我用更高级别的迭代结构替换了你所有的循环。作为一般规则,如果您在 Ruby 中编写循环,那么您就做错了。
Enumerable
中有很多强大的方法,你永远不需要循环。
我还写了一些测试来证明 任何 class 在 Vektor
之外,我们现在支持 int * vek
的乘法, vek * int
, float * vek
, vek * float
, rational * vek
, vek * rational
, complex * vek
, 和 vek * complex
, 只需实现 Numeric#coerce
强制协议。
require 'test/unit'
class VektorTest < Test::Unit::TestCase
def test_that_creating_an_empty_vektor_actually_creates_a_zero_vektor_of_dimension_3
v = Vektor.new
assert_equal 3, v.size
end
def test_that_square_brackets_is_an_alias_for_new
v = Vektor[]
assert_equal 3, v.size
end
def test_that_we_can_multiply_two_trivial_vektors
v1 = Vektor[2]
v2 = Vektor[3]
assert_equal 6, v1 * v2
end
def test_that_we_can_multiply_two_nontrivial_vektors
v1 = Vektor[2, 3, 4]
v2 = Vektor[5, 6, 7]
assert_equal 56, v1 * v2
end
def test_that_we_can_multiply_a_trivial_vektor_with_an_integer
v = Vektor[2]
assert_equal Vektor[6], v * 3 # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_trivial_vektor_with_an_integer_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { v * 3 }
end
def test_that_we_can_multiply_a_nontrivial_vektor_with_an_integer
v = Vektor[2, 3, 4]
assert_equal Vektor[6, 9, 12], v * 3 # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_nontrivial_vektor_with_an_integer_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { v * 3 }
end
def test_that_we_can_multiply_an_integer_with_a_trivial_vektor
v = Vektor[2]
assert_equal Vektor[6], 3 * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_an_integer_with_a_trivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { 3 * v }
end
def test_that_we_can_multiply_an_integer_with_a_nontrivial_vektor
v = Vektor[2, 3, 4]
assert_equal Vektor[6, 9, 12], 3 * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_an_integer_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { 3 * v }
end
def test_that_we_can_multiply_a_trivial_vektor_with_a_float
v = Vektor[2]
assert_equal Vektor[6.0], v * 3.0 # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_trivial_vektor_with_a_float_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { v * 3.0 }
end
def test_that_we_can_multiply_a_nontrivial_vektor_with_a_float
v = Vektor[2, 3, 4]
assert_equal Vektor[6.0, 9.0, 12.0], v * 3.0 # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_nontrivial_vektor_with_an_float_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { v * 3.0 }
end
def test_that_we_can_multiply_a_float_with_a_trivial_vektor
v = Vektor[2]
assert_equal Vektor[6.0], 3.0 * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_float_with_a_trivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { 3.0 * v }
end
def test_that_we_can_multiply_a_float_with_a_nontrivial_vektor
v = Vektor[2, 3, 4]
assert_equal Vektor[6.0, 9.0, 12.0], 3.0 * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_float_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { 3.0 * v }
end
def test_that_we_can_multiply_a_trivial_vektor_with_a_rational
v = Vektor[2]
assert_equal Vektor[6r], v * 3r # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_trivial_vektor_with_a_rational_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { v * 3r }
end
def test_that_we_can_multiply_a_nontrivial_vektor_with_a_rational
v = Vektor[2, 3, 4]
assert_equal Vektor[6r, 9r, 12r], v * 3r # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_nontrivial_vektor_with_an_rational_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { v * 3r }
end
def test_that_we_can_multiply_a_rational_with_a_trivial_vektor
v = Vektor[2]
assert_equal Vektor[6r], 3r * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_rational_with_a_trivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { 3r * v }
end
def test_that_we_can_multiply_a_rational_with_a_nontrivial_vektor
v = Vektor[2, 3, 4]
assert_equal Vektor[6r, 9r, 12r], 3r * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_rational_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { 3r * v }
end
def test_that_we_can_multiply_a_trivial_vektor_with_a_complex_number
v = Vektor[2]
assert_equal Vektor[6i], v * 3i # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_trivial_vektor_with_a_complex_number_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { v * 3i }
end
def test_that_we_can_multiply_a_nontrivial_vektor_with_a_complex_number
v = Vektor[2, 3, 4]
assert_equal Vektor[6i, 9i, 12i], v * 3i # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_nontrivial_vektor_with_an_complex_number_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { v * 3i }
end
def test_that_we_can_multiply_a_complex_number_with_a_trivial_vektor
v = Vektor[2]
assert_equal Vektor[6i], 3i * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_complex_number_with_a_trivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { 3i * v }
end
def test_that_we_can_multiply_a_complex_number_with_a_nontrivial_vektor
v = Vektor[2, 3, 4]
assert_equal Vektor[6i, 9i, 12i], 3i * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_complex_number_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { 3i * v }
end
end
我们还添加一些您通常总是需要实现的方法,以使您的对象与 Ruby 生态系统的其余部分一起工作:
class Vektor
def ==(other)
x == other.x
end
def eql?(other)
other.is_a?(Vektor) && self == other
end
def hash
x.hash
end
def to_s
"(#{x.join(', ')})"
end
def inspect
"Vektor#{x.inspect}"
end
end
如果您绝对必须使用猴子补丁,那么您保留对旧版本方法的访问权限很重要你在修补猴子。执行此操作的工具是 Module#prepend
方法。它看起来像这样:
class Vektor
def self.inherited(*)
raise TypeError, "#{self} is immutable and cannot be inherited from."
end
attr_reader :x # `x` should not be writeable by anybody!
def initialize(*vektors)
self.x = if vektors.size.zero?
Array.new(3, 0)
else
vektors
end.freeze
end
singleton_class.alias_method :[], :new
alias_method :length, def size
x.size
end
def *(other)
case other
when Vektor
raise ArgumentError, "the vektors don't have the same length!" if size != other.size
x.zip(other.x).map {|a, b| a * b }.sum
when Numeric
self.class.new(*x.map(&other.method(:*)))
end
end
private
attr_writer :x
freeze
end
( 大部分相同,但没有 coerce
方法,也没有 case
表达式中的 else
子句,并且 x
reader 需要是 public
.)
module IntegerTimesVektorExtension
def *(other)
return Vektor[*other.x.map(&method(:*))] if other.is_a?(Vektor)
super
end
end
而且因为猴子补丁核心 classes 真的很危险,我们使用 改进 ,以确保猴子补丁只在你显式 激活细化using IntegerTimesVektorRefinement
。
module IntegerTimesVektorRefinement
refine Integer do
prepend IntegerTimesVektorExtension
end
end
现在,我们可以这样做:
v = Vektor[2, 3, 4]
5 * v
# `*': Vektor can't be coerced into Integer (TypeError)
using IntegerTimesVektorRefinement
5 * v
#=> <#<Vektor:0x00007fcc88868588 @x=[10, 15, 20]>
In the dark old times, before Module#prepend
existed, we had to resort to other dirty tricks 以便能够保留 monkey-patched 方法。但是,自 2013 年 2 月 24 日 Ruby 2.0 发布以来,其中包括 Module#prepend
,这些技巧不再是必需的,并且 不应使用,也不应教授 .这包括 alias_method
连锁技巧,看起来像这样:
class Integer
alias_method :original_mul, :*
def *(other)
return Vektor[*other.x.map(&method(:*))] if other.is_a?(Vektor)
original_mul(other)
end
end
但是,如前所述:您不应该这样做。
最好的解决方案是实施 coerce
协议。真的不是 "just" 最好的解决方案,而是 唯一正确的 解决方案。
如果出于某种原因,您不想实施coerce
协议,那么最好的解决方案是Module#prepend
。理想情况下进行细化,但请注意并非所有 Ruby 实现都实现细化。
如果你真的,真的,真的,必须做猴子补丁,并且使用的是十年前的、不受支持的、未维护的、过时的、过时的 Ruby 版本,因此不能使用 Module#prepend
,还有还是 better solutions out there than alias_method
,比如把实例方法抓取成Method
对象,存到局部变量里,你close过来
在这里使用 alias_method
是没有必要的,如果不是完全 错误的 和糟糕的、过时的、过时的做法。
我是 ruby 的新手,正在尝试了解猴子补丁。
所以在练习一个简单的 Vektor 计算器的旧项目时。
我写了下面的代码,它允许我将一个 Vektor 与一个数字和两个向量相乘:
class Vektor
attr_accessor :x #instead of getter and setter!-
def initialize (*vektors)
if vektors.length==0
@x=Array.new(3,0)
else
@x=vektors
end
end
def size
@x.length
end
def *(other)
case other
when Vektor
if (self.size != other.size)
return "the vektors don't have the same length!"
else
result=0
for i in 0 ... other.size
result += (self.x[i] * other.x[i])
end
return result
end
when Numeric
for i in 0 ... self.size
self.x[i]*=other
end
return self
end
end
end
这里是整数 class 所以我可以按照这样的示例以其他方式进行乘法 -> 5*vektor
class Integer
def mul(other)
for i in 0 ... other.size
other.x[i]*=self # this is the cause of the error!
end
return other
end
alias_method :* , :mul
end
obj1=Vektor.new(2,2,2)
puts 3*obj1
我的问题是出现错误,我不知道为什么,但我可以假设这是因为我在 alias_method
中使用了 *
在 mul
方法中使用。
输出如下:
undefined method `x' for 3:Integer (NoMethodError)
已编辑:
作业文本:
Extend the Integer class with a monkey patch so that you can write 5 * vector instead of vector * 5.
Test whether this patch works via a suitable unit test.
Tip: You will want to use the alias keyword or the alias_method method to achieve the goal.
我完全理解问题的原因,但我似乎找不到可以防止错误发生的解决方法!
这就是我目前正在做的事情,但工作方式不对:(
class Integer
def mul(other)
case (other)
when Numeric
return self*other
when Vektor
for i in 0 ... other.size
other.x[i]*=self # this is the cause of the error!
end
return other
end
end
alias_method :**,:mul
end
obj1=Vektor.new(2,2,2)
puts obj1*3
puts 3**Vektor.new(3,3,3)
请注意,此时我所关心的只是扩展我的知识,而不是得到一个愚蠢的笔记。 :)
other.x[i]*=self
与
相同other.x[i] = other.x[i] * self
或者让它变得非常明确
other.x().[]=(i)(other.x().[](i).*(self))
other.x[i]
是一个 Integer
,self
也是一个 Integer
(在本例中是 3
)。
在你的Integer
方法中,你调用了other.x
,但是当你乘以some_integer * 3
,那么在你的Integer#*
方法中,other
是3
,所以你正在调用 3.x
,它确实不存在。
请注意,您的 Vektor#*
方法现在也已损坏,因为它也需要乘以两个 Integer
,但您只是覆盖了知道如何乘以的 Integer#*
方法将两个 Integer
相乘(实际上也知道如何将 Integer
与 任何正确实现 Numeric#coerce
协议的对象 相乘)与一个只知道如何乘以一个 Integer
和一个 Vektor
,因此不再知道如何乘以两个 Integer
。
另一个问题是,在您的 Vektor#*
方法中,您检查 other
是否是 Numeric
的一个实例,但反过来您只实现 [=22] 的乘法=]秒。这使您的乘法不对称,例如some_vektor * 1.0
可以,但 1.0 * some_vektor
不行。
正确的实现是完全不触及 Integer
,而只是实现数字强制协议。这将解决您所有的问题:您不必猴子修补 anything,您的 Vektor
s 将自动与 any[=155 一起工作=] Numeric
在核心库、标准库、Ruby 生态系统中,实际上什至还没有被编写出来。像这样:
class Vektor
def self.inherited(*)
raise TypeError, "#{self} is immutable and cannot be inherited from."
end
def initialize(*vektors)
self.x = if vektors.size.zero?
Array.new(3, 0)
else
vektors
end.freeze
end
singleton_class.alias_method :[], :new
alias_method :length, def size
x.size
end
def coerce(other)
[self, other]
end
def *(other)
case other
when Vektor
raise ArgumentError, "the vektors don't have the same length!" if size != other.size
x.zip(other.x).map {|a, b| a * b }.sum
when Numeric
self.class.new(*x.map(&other.method(:*)))
else
a, b = other.coerce(self)
a * b
end
end
protected
attr_reader :x # `x` should not be writeable by anybody!
private
attr_writer :x
freeze
end
您会注意到我对您的代码做了一些更改:
Vektor
现在是不可变的,Vektor#*
return 是一个新的Vektor
而不是变异的self
。尽可能保持对象不可变通常是个好主意,但它 尤其是 对于 "number-like" 对象(如Vektor
s。如果2 * 3
没有 return6
而是让2
具有值6
,你会非常惊讶,不是吗?但这正是您的代码对Vektor
s! 所做的
x
不再对所有人公开,最重要的是,不再对所有人 可写 。- 我用更高级别的迭代结构替换了你所有的循环。作为一般规则,如果您在 Ruby 中编写循环,那么您就做错了。
Enumerable
中有很多强大的方法,你永远不需要循环。
我还写了一些测试来证明 任何 class 在 Vektor
之外,我们现在支持 int * vek
的乘法, vek * int
, float * vek
, vek * float
, rational * vek
, vek * rational
, complex * vek
, 和 vek * complex
, 只需实现 Numeric#coerce
强制协议。
require 'test/unit'
class VektorTest < Test::Unit::TestCase
def test_that_creating_an_empty_vektor_actually_creates_a_zero_vektor_of_dimension_3
v = Vektor.new
assert_equal 3, v.size
end
def test_that_square_brackets_is_an_alias_for_new
v = Vektor[]
assert_equal 3, v.size
end
def test_that_we_can_multiply_two_trivial_vektors
v1 = Vektor[2]
v2 = Vektor[3]
assert_equal 6, v1 * v2
end
def test_that_we_can_multiply_two_nontrivial_vektors
v1 = Vektor[2, 3, 4]
v2 = Vektor[5, 6, 7]
assert_equal 56, v1 * v2
end
def test_that_we_can_multiply_a_trivial_vektor_with_an_integer
v = Vektor[2]
assert_equal Vektor[6], v * 3 # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_trivial_vektor_with_an_integer_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { v * 3 }
end
def test_that_we_can_multiply_a_nontrivial_vektor_with_an_integer
v = Vektor[2, 3, 4]
assert_equal Vektor[6, 9, 12], v * 3 # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_nontrivial_vektor_with_an_integer_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { v * 3 }
end
def test_that_we_can_multiply_an_integer_with_a_trivial_vektor
v = Vektor[2]
assert_equal Vektor[6], 3 * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_an_integer_with_a_trivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { 3 * v }
end
def test_that_we_can_multiply_an_integer_with_a_nontrivial_vektor
v = Vektor[2, 3, 4]
assert_equal Vektor[6, 9, 12], 3 * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_an_integer_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { 3 * v }
end
def test_that_we_can_multiply_a_trivial_vektor_with_a_float
v = Vektor[2]
assert_equal Vektor[6.0], v * 3.0 # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_trivial_vektor_with_a_float_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { v * 3.0 }
end
def test_that_we_can_multiply_a_nontrivial_vektor_with_a_float
v = Vektor[2, 3, 4]
assert_equal Vektor[6.0, 9.0, 12.0], v * 3.0 # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_nontrivial_vektor_with_an_float_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { v * 3.0 }
end
def test_that_we_can_multiply_a_float_with_a_trivial_vektor
v = Vektor[2]
assert_equal Vektor[6.0], 3.0 * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_float_with_a_trivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { 3.0 * v }
end
def test_that_we_can_multiply_a_float_with_a_nontrivial_vektor
v = Vektor[2, 3, 4]
assert_equal Vektor[6.0, 9.0, 12.0], 3.0 * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_float_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { 3.0 * v }
end
def test_that_we_can_multiply_a_trivial_vektor_with_a_rational
v = Vektor[2]
assert_equal Vektor[6r], v * 3r # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_trivial_vektor_with_a_rational_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { v * 3r }
end
def test_that_we_can_multiply_a_nontrivial_vektor_with_a_rational
v = Vektor[2, 3, 4]
assert_equal Vektor[6r, 9r, 12r], v * 3r # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_nontrivial_vektor_with_an_rational_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { v * 3r }
end
def test_that_we_can_multiply_a_rational_with_a_trivial_vektor
v = Vektor[2]
assert_equal Vektor[6r], 3r * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_rational_with_a_trivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { 3r * v }
end
def test_that_we_can_multiply_a_rational_with_a_nontrivial_vektor
v = Vektor[2, 3, 4]
assert_equal Vektor[6r, 9r, 12r], 3r * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_rational_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { 3r * v }
end
def test_that_we_can_multiply_a_trivial_vektor_with_a_complex_number
v = Vektor[2]
assert_equal Vektor[6i], v * 3i # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_trivial_vektor_with_a_complex_number_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { v * 3i }
end
def test_that_we_can_multiply_a_nontrivial_vektor_with_a_complex_number
v = Vektor[2, 3, 4]
assert_equal Vektor[6i, 9i, 12i], v * 3i # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_nontrivial_vektor_with_an_complex_number_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { v * 3i }
end
def test_that_we_can_multiply_a_complex_number_with_a_trivial_vektor
v = Vektor[2]
assert_equal Vektor[6i], 3i * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_complex_number_with_a_trivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { 3i * v }
end
def test_that_we_can_multiply_a_complex_number_with_a_nontrivial_vektor
v = Vektor[2, 3, 4]
assert_equal Vektor[6i, 9i, 12i], 3i * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_complex_number_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { 3i * v }
end
end
我们还添加一些您通常总是需要实现的方法,以使您的对象与 Ruby 生态系统的其余部分一起工作:
class Vektor
def ==(other)
x == other.x
end
def eql?(other)
other.is_a?(Vektor) && self == other
end
def hash
x.hash
end
def to_s
"(#{x.join(', ')})"
end
def inspect
"Vektor#{x.inspect}"
end
end
如果您绝对必须使用猴子补丁,那么您保留对旧版本方法的访问权限很重要你在修补猴子。执行此操作的工具是 Module#prepend
方法。它看起来像这样:
class Vektor
def self.inherited(*)
raise TypeError, "#{self} is immutable and cannot be inherited from."
end
attr_reader :x # `x` should not be writeable by anybody!
def initialize(*vektors)
self.x = if vektors.size.zero?
Array.new(3, 0)
else
vektors
end.freeze
end
singleton_class.alias_method :[], :new
alias_method :length, def size
x.size
end
def *(other)
case other
when Vektor
raise ArgumentError, "the vektors don't have the same length!" if size != other.size
x.zip(other.x).map {|a, b| a * b }.sum
when Numeric
self.class.new(*x.map(&other.method(:*)))
end
end
private
attr_writer :x
freeze
end
( 大部分相同,但没有 coerce
方法,也没有 case
表达式中的 else
子句,并且 x
reader 需要是 public
.)
module IntegerTimesVektorExtension
def *(other)
return Vektor[*other.x.map(&method(:*))] if other.is_a?(Vektor)
super
end
end
而且因为猴子补丁核心 classes 真的很危险,我们使用 改进 ,以确保猴子补丁只在你显式 激活细化using IntegerTimesVektorRefinement
。
module IntegerTimesVektorRefinement
refine Integer do
prepend IntegerTimesVektorExtension
end
end
现在,我们可以这样做:
v = Vektor[2, 3, 4]
5 * v
# `*': Vektor can't be coerced into Integer (TypeError)
using IntegerTimesVektorRefinement
5 * v
#=> <#<Vektor:0x00007fcc88868588 @x=[10, 15, 20]>
In the dark old times, before Module#prepend
existed, we had to resort to other dirty tricks 以便能够保留 monkey-patched 方法。但是,自 2013 年 2 月 24 日 Ruby 2.0 发布以来,其中包括 Module#prepend
,这些技巧不再是必需的,并且 不应使用,也不应教授 .这包括 alias_method
连锁技巧,看起来像这样:
class Integer
alias_method :original_mul, :*
def *(other)
return Vektor[*other.x.map(&method(:*))] if other.is_a?(Vektor)
original_mul(other)
end
end
但是,如前所述:您不应该这样做。
最好的解决方案是实施 coerce
协议。真的不是 "just" 最好的解决方案,而是 唯一正确的 解决方案。
如果出于某种原因,您不想实施coerce
协议,那么最好的解决方案是Module#prepend
。理想情况下进行细化,但请注意并非所有 Ruby 实现都实现细化。
如果你真的,真的,真的,必须做猴子补丁,并且使用的是十年前的、不受支持的、未维护的、过时的、过时的 Ruby 版本,因此不能使用 Module#prepend
,还有还是 better solutions out there than alias_method
,比如把实例方法抓取成Method
对象,存到局部变量里,你close过来
在这里使用 alias_method
是没有必要的,如果不是完全 错误的 和糟糕的、过时的、过时的做法。