2

この単純なクラスがあるとします。

class Color
  attr_accessor :rgb
  def initialize(ary)
    @rgb = ary
  end
  def +(other)
    other = Color.new(other) unless Color === other
    Color.new(@rgb.zip(other.rgb).map {|p| [p.reduce(:+), 255].min })
  end
end

これはそれを実装するための悪い方法であることを私は知っていますが、これは私が考えることができる最短の方法です。

c100 = Color.new([100, 100, 100])
c100 + c100         #=> Color(200, 200, 200)
c100 + c100 + c100  #=> Color(255, 255, 255)

配列を色として指定した場合にも機能します。

c100 + [50, 50, 50] #=> Color(150, 150, 150)

しかし、私はこれを行うことはできません:

[50, 50, 50] + c100 #=> TypeError: can't convert Color into Array

定義coerceは機能しません。どうすればそれを機能させることができますか?

4

2 に答える 2

3

それはコードが

[50, 50, 50] + c100

+ColorではなくArrayのメソッドを呼び出し、そのメソッドは色をArrayに変換できません。

対照的に、

 c100 + [50, 50, 50]

Colorの+メソッドを呼び出します。

ただし、Colorで変換方法を定義した場合でも、次のようになります。

class Color
def to_ary
return @rgb
end
end

Arrayメソッドは期待どおりに機能しません。+Arrayのメソッドは要素を追加するのではなく、オペランドを連結するため、結果は2つの配列の連結になります。

irb>[50,50,50]+c100
=> [50,50,50,100,100,100]

ここでは、結果は色ではなく配列になります。

編集:

これを機能させる唯一の方法は、配列のメソッドをエイリアスし+て、2番目のオペランドとしてColorを受け取るという特殊なケースを処理することです。ただし、このアプローチはかなり醜いことを認めます。

class Array
  alias color_plus +
  def +(b)
    if b.is_a?(Color)
      return b+self
    end
    return color_plus(b)
  end
end
于 2011-09-25T23:02:59.143 に答える
1

@ 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#*

于 2012-07-07T19:00:27.983 に答える