【问题标题】:monkey patching the * operator猴子修补 * 运算符
【发布时间】:2020-08-09 14:33:37
【问题描述】:

我是 ruby​​ 新手,并试图了解猴子补丁。

所以在练习一个简单的 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

这里是整数类,所以我可以像这样以其他方式进行乘法运算 -> 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 中使用了 *,而它在 @ 内部使用987654326@方法。

输出如下:

undefined method `x' for 3:Integer (NoMethodError)

编辑:

作业文本:

用猴子补丁扩展 Integer 类,这样你就可以写 5 * 向量而不是向量 * 5。

通过合适的单元测试测试此补丁是否有效。

提示:您将需要使用 alias 关键字或 alias_method 方法 实现目标。

我完全理解问题的原因,但我似乎找不到可以防止错误发生的解决方法!。

这就是我目前的工作,它正在工作,但以错误的方式:(

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)

请注意,我现在关心的只是扩展我的知识,而不是得到一个愚蠢的笔记。 :)

【问题讨论】:

  • when Numeric 块中,您调用self.x[i]*=other。如果我的理解是正确的,self.x[i] 是一个数字,所以这最终会调用<number>.mul(<number>)。所以我认为您需要在mul 方法中进行类型检查,以查看otherNumeric 还是Vektor。虽然我没有完全按照你的逻辑来这里

标签: ruby operator-overloading monkeypatching


【解决方案1】:
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#* 方法现在也已损坏,因为它也需要将两个 Integers 相乘,但您只是重写了知道如何将两个 Integers 相乘的 Integer#* 方法(以及实际上也知道如何将Integer 与正确实现Numeric#coerce 协议的任何对象 相乘)与只知道如何将IntegerVektor 相乘的对象,因此没有更知道如何将两个Integers 相乘。

另一个问题是,在您的Vektor#* 方法中,您检查other 是否是Numeric 的一个实例,但反过来您只为Integers 实现乘法。这使您的乘法不对称,例如some_vektor * 1.0 可以,但1.0 * some_vektor 不行。

正确的实现是完全不接触Integer,而只是实现数字强制协议。这将解决您的所有问题:您不必对 anything 进行猴子补丁,并且您的 Vektors 将自动与 any 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

您会注意到我对您的代码做了一些更改:

  • Vektors 现在是不可变的,Vektor#* 返回一个新的Vektor,而不是改变self。尽可能保持对象不可变通常是个好主意,但对于“类似数字”的对象,例如Vektors,尤其非常重要(并且确实是预期的)。如果2 * 3 没有返回6 而是让2 具有6 的值,您会感到非常惊讶,不是吗?但这正是您的代码对 Vektors 所做的事情!
  • x 不再向所有人公开,最重要的是,不再为所有人可写
  • 我用更高级别的迭代构造替换了所有循环。作为一般规则,如果您在 Ruby 中编写循环,那么您做错了什么。 Enumerable 中有很多强大的方法,你永远不需要循环。

我还编写了一些测试来证明即使不触及Vektor 之外的任何 类,我们现在也支持int * vekvek * intfloat * vekvek * float 的乘法, rational * vekvek * rationalcomplex * vekvek * 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

(大部分相同,但在case 表达式中没有coerce 方法和else 子句,并且x 读取器需要为public。)

module IntegerTimesVektorExtension
  def *(other)
    return Vektor[*other.x.map(&method(:*))] if other.is_a?(Vektor)
    super
  end
end

因为猴子补丁核心类确实很危险,我们使用细化,以确保猴子补丁仅在您显式激活细化时才有效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 能够保留猴子补丁方法。但是,从 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 协议。真的不是“只是”最好的解决方案,而是唯一正确的解决方案。

如果出于某种原因您不想实现coerce 协议,那么最好的解决方案是Module#prepend。理想情况下需要优化,但请注意并非所有 Ruby 实现都实现了优化。

如果你真的,真的,真的,必须做猴子补丁,并且正在使用一个十年前的、不受支持、未维护、过时、过时的 Ruby 版本,因此不能使用Module#prepend,那么有 仍然 better solutions out there than alias_method,例如获取实例方法作为Method 对象并将其存储在您关闭的局部变量中。

这里没有必要使用alias_method,如果不是完全错误,也是不好的、过时的、过时的做法。

【讨论】:

  • 非常感谢您花时间和精力来编写所有这些内容,但首先我想说的是,很遗憾我必须使用猴子补丁来完成它,因为它是一项任务。再加上Vektor*,我必须检查另一个是否是数字,这样我就可以在两个向量之间执行与 skalar 和 skalar 乘法的乘法!但是您很好地解释了错误的来源,但还检查了 Integer* 中的其他是否为数字,然后返回 self*other 引发 stack too deep error
  • 如果您对Integer#* 方法进行猴子修补,那么知道如何将两个整数相乘的原始方法不再存在。但是,由于您在Integer#*Vektor#* 方法中需要此方法,因此您获得的分配是不可能的。正确的解决方案是使用 Ruby 的算术强制协议,它允许所有其他类自动使用Vektor,而无需您做任何事情。 (这不可能并不完全正确,但它要复杂得多。我会说肯定高于初学者课程。)
  • 请再次查看帖子我添加了作业文本(从德语翻译)并添加了我目前所在的位置!。
猜你喜欢
  • 2012-10-13
  • 2012-03-13
  • 2013-12-26
  • 2012-06-14
  • 2012-03-29
  • 2016-10-30
  • 2019-03-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多