Ruby:仅在某些情况下重载运算符行为

Ruby: overload operator behaviour for some cases only

我的问题是:如何在内置 class(例如 Integer.new.+)上重载运算符,但仅限于某些情况,具体取决于 class第二个操作数

这是我正在寻找的行为:

myObject = myClass.new
1 + myObject #=> special behaviour
1 + 2        #=> default behaviour (3)

例如,在 Python 中,我会在 myClass 上定义一个 __radd__ 方法来覆盖案例 1。

我试过使用 super 但显然 Numeric 没有运算符方法。

理想情况下,我正在寻找一种提取 + 方法并重命名它的方法。

像这样:

class Integer
  self.plus = self.+  # you know what i mean, I don't know how else to express this.
                      # I also know that methods don't work like this, this is just to
                      # illustrate a point.
  def + other
    other.class == myClass ? special behaviour : self.plus other
  end
end

感谢您的帮助

到目前为止,此处发布的两种方法都是遗留 Rails 方法,完全错误。它依赖于 class 没有名为 plus 的方法 并且 没有人会重新打开 class 来创建名为 [=11= 的方法].否则事情会变得疯狂。

正确的解法是Module#prepend:

Integer.prepend(Module.new do
  def + other
    case other
    when Fixnum then special_behaviour
    else super(other)
    end
  end
end)

是的,您可以重写标准库中几乎所有内容的行为来实现某个结果,但这会损害对代码的理解,并在将来的某个时候回来咬您一口。

在这种特殊情况下,Fixnum#+ 设计为采用数值,return 设计为采用数字结果。如果我们想定义自己的 classes 来与 Fixnum#+ 交互,我们需要理解设计契约并遵守它。

Ruby 中的一般约定是使用鸭子类型。我们不关心对象的class,我们只关心它的行为是否像/可以转换成我们想要的对象。例如:

class StringifiedNumber
  def initialize(number)
    @number = number
  end

  # String#+ calls to_str on any object passed to it

  def to_str
    # replace with number to string parsing logic
    "one hundred"
  end
end 

> "total: " + StringifiedNumber.new(100)
=> "total: one hundred"

数字有点复杂,因为您可以混合使用整数、浮点数、复数等。处理此问题的惯例是定义一个 coerce 方法,其中 return 有两个元素相同类型的,然后用于执行请求的操作。

class NumberfiedString
  def initialize(string)
    @string = string
  end

  def to_i
    # replace with complicated natural language parsing logic
    100
  end

  def +(other_numberfied_string)
    NumberfiedString.new(self.to_i + other_numberfied_string.to_i)
  end

  # For types which are not directly supported,
  # Fixnum#+(target) will call the equivalent of 
  # target.coerce[0] + target.coerce[1] 

  def coerce(other)
    [NumberfiedString.new(other.to_s), self]
  end
end

> NumberfiedString.new("one hundred") + NumberfiedString.new("one hundred")
=> #<NumberfiedString:0x007fadbc036d28 @string=200>

> 100 + NumberfiedString.new("one hundred") 
=> #<NumberfiedString:0x007fadbc824c88 @string="200">

回答 OP 的后续问题:

Is there no equivalent to Python's radd and related methods? (Where, if the first operand doesn't support the operation or the types, the second operand takes over)

class MyClass
  def +(other)
    puts "called +"
  end

  def coerce(other)
    [self, other]
  end
end

> 1 + MyClass.new
called +
=> nil