@ peter-oの回答についてさらに詳しく説明すると、この実装を思いついたのですが、配列のいくつかのメソッドを再定義するという意味では醜いですが、予想される動作の回避策としてはかなりうまくいくと思います。これをプロダクションコードに適合させることはできましたが、私はこのチャレンジが本当に気に入りました...色の主題について分岐して申し訳ありませんが、マイナスと時間の予想される動作がどうなるかわかりませんでした。
class Array
alias :former_plus :+
alias :former_minus :-
alias :former_times :*
def +(other)
former_plus(other)
rescue TypeError
apply_through_coercion(other, :+)
end
def -(other)
former_minus(other)
rescue TypeError
apply_through_coercion(other, :-)
end
def *(other)
former_times(other)
rescue TypeError
apply_through_coercion(other, :*)
end
# https://github.com/ruby/ruby/blob/ruby_1_9_3/lib/matrix.rb#L1385
def apply_through_coercion(obj, oper)
coercion = obj.coerce(self)
raise TypeError unless coercion.is_a?(Array) && coercion.length == 2
coercion[0].public_send(oper, coercion[1])
rescue
raise TypeError, "#{obj.inspect} can't be coerced into #{self.class}"
end
private :apply_through_coercion
end
課題の1つは、メソッドの逆呼び出しがPoint#-
予期しない結果を返さないようにすることでした。したがって、@coerced
インスタンス変数はオブジェクトの制御フラグとして返されます。
class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y, @coerced = x, y, false
end
def coerce(other)
@coerced = true
[self, other]
end
def coerced?; @coerced end
def +(other)
other = Point.new(*other) if other.respond_to? :to_ary
Point.new(@x + other.x, @y + other.y)
end
def -(other)
other = Point.new(*other) if other.respond_to? :to_ary
if coerced?
@coerced = false; other + (-self)
else self + (-other) end
end
def -@; Point.new(-@x, -@y) end
def *(other)
case other
when Fixnum then Point.new(@x*other, @y*other)
when Point then Point.new(@x*other.x, @y*other.y)
when Array then self * Point.new(*other)
end
end
end
結局のところ、このコードが何とか達成できるのは、それが存在しなかったArrayクラスに、明示的にメソッドとに強制機能を追加するArray#+
ことArray#-
ですArray#*
。