Ruby:仅针对某些情况的重载操作符行为

时间:2017-04-13 03:39:55

标签: ruby methods operator-overloading operator-keyword

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

这是我正在寻找的行为:

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

感谢您的帮助

2 个答案:

答案 0 :(得分:3)

到目前为止,这里发布的两种方法都是传统的Rails方式,明显错误。它依赖于这样一个事实:该类没有名为plus 的方法,而 nobody将重新打开该类以创建一个名为plus的方法。否则事情就会发疯。

正确的解决方案是Module#prepend

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

答案 1 :(得分:2)

是的,你可以覆盖标准库中几乎任何东西的行为来实现结果,但这会损害对代码的理解,并在将来的某个时候回来咬你。

在这种特殊情况下,Fixnum#+旨在获取数值并返回数值结果。如果我们想要定义自己的类来与Fixnum#+进行交互,我们需要了解设计合同并遵守它。

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

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方法,它返回两个相同类型的元素然后用于执行请求的操作。

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的后续问题:

  

没有相当于Python的radd和相关方法吗? (哪里,   如果第一个操作数不支持操作或类型,则   第二个操作数接管)

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

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

> 1 + MyClass.new
called +
=> nil