猴子修补 * 运算符

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] 是一个 Integerself 也是一个 Integer(在本例中是 3)。 在你的Integer方法中,你调用了other.x,但是当你乘以some_integer * 3,那么在你的Integer#*方法中,other3,所以你正在调用 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,您的 Vektors 将自动与 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" 对象(如 Vektors。如果 2 * 3 没有 return 6 而是让 2 具有值 6,你会非常惊讶,不是吗?但这正是您的代码对 Vektors!
  • 所做的
  • 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 是没有必要的,如果不是完全 错误的 和糟糕的、过时的、过时的做法。