Ruby 运算符重载问题

14

我为了娱乐而尝试使用 Ruby 和 OpenGL,决定编写一些 3D 向量/平面等类来优化数学代码。

简单示例:

class Vec3
    attr_accessor :x,:y,:z

    def *(a)
        if a.is_a?(Numeric) #multiply by scalar
            return Vec3.new(@x*a, @y*a, @z*a)
        elsif a.is_a?(Vec3) #dot product
            return @x*a.x + @y*a.y + @z*a.z
        end
    end
end

v1 = Vec3.new(1,1,1)
v2 = v1*5 #produces [5,5,5]

这都很好,但我也想能够编写。

v2 = 5*v1

我想要在Fixnum或Float等对象上添加功能,但是我无法找到一种方法来重载或扩展fixnum的乘法而不完全替换它。在Ruby中是否有可能实现这一点?有什么建议吗?

(显然,如果需要,我可以按正确顺序编写所有乘法)


只是为了记录,将 @x*s, @y*s, @z*s 更改为 @x*a, @y*a, @z*a,否则你的代码会出错。 - Chris Lutz
谢谢,代码同时从两个地方复制><现在应该已经修复了。 - user225620
2个回答

24

使用 coerce 比猴子补丁一个核心类的方法要好得多:

class Vec3
    attr_accessor :x,:y,:z

    def *(a)
        if a.is_a?(Numeric) #multiply by scalar
            return Vec3.new(@x*a, @y*a, @z*a)
        elsif a.is_a?(Vec3) #dot product
            return @x*a.x + @y*a.y + @z*a.z
        end
    end

    def coerce(other)
        return self, other
    end
end
如果您将v定义为v = Vec3.new,那么以下内容将起作用:v * 55 * v。 coerce(self)返回的第一个元素将成为操作的新接收者,第二个元素(other)将成为参数,因此5 * v等价于v * 5

2
在编写代码时,请注意不要对核心类进行猴子补丁,除非确实有绝对必要。请记住,在调试您的代码时,您的同事和后续开发者会感激您的规范性和认真性。在需要强制转换时优先考虑 +1。 - zenazn
这对我所需的工作非常好。如果我遇到一个不能交换的类似例子,那么我想我会根据需要进行猴子补丁 ;) - user225620
强制转换应该始终将self作为第二个参数返回!否则,您会破坏参数的可交换性。相反,coerce应该将参数转换为可以进行乘法运算的类型(例如:Vec3.new(other, other, other))。显然,这也不是没有问题的。 - R. Peereboom

0

我相信以下代码可以实现你想要的功能,尽管Banister的建议使用coerce而不是猴子补丁Numeric是一种更好的方法。只有在必要时才使用此方法(例如,如果您只想使某些二进制操作数具有传递性)。

Fixnum.class_eval do
  original_times = instance_method(:*)
  define_method(:*) do |other|
    if other.kind_of?(Vec3)
      return other * self
    else
      return original_times.bind(self).call(other)
    end
  end
end

1
嗯,很性感 :) 顺便说一下,我需要将第一行更改为“Fixnum.class_eval do”或(等效的)“class Fixnum”。 - user225620
1
直接在Fixnum类上定义它而不使用class_eval,并进行常规的def,这不可能吗? - horseyguy
1
alias_method 不必要地污染了命名空间。这种习惯用法既不是“荒谬的”(它是一个众所周知的习惯用法,其内部工作以及必要性已经在许多博客文章中得到了充分的记录),也不是“完全没有必要的”(事实上,据我所知,这是唯一已知可行的习惯用法)。 - Jörg W Mittag
1
栏杆对于 coerce 的建议是解决可交换性问题的更好方法。+1 支持它。 - James A. Rosen
1
哦,还有栏杆:对于Numeric来说,“普通类体”可以正常工作,因为它在这一点上肯定已经定义了,但在其他情况下是危险的,因为它可能会阻止“autoload”语句的触发。 - James A. Rosen
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接