Принуждение из массивов - PullRequest
2 голосов
/ 26 сентября 2011

Предположим, у меня есть простой класс:

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 не работает.Как я могу заставить его работать?

Ответы [ 2 ]

3 голосов
/ 26 сентября 2011

Это потому, что код

[50, 50, 50] + c100

вызывает метод + для массива, а не для цвета, и этот метод не может преобразовать цвет в массив.

В отличие от

 c100 + [50, 50, 50]

вызывает метод Color +.

Однако, даже если вы определите метод преобразования в Color:

class Color
def to_ary
return @rgb
end
end

метод Array не будет работать так, как вы ожидаете; результатом будет объединение двух массивов, поскольку метод + массива объединяет их операнды вместо добавления их элементов:

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

Здесь результатом будет массив, а не цвет.

EDIT:

Единственный способ сделать эту работу, который я вижу, - это псевдоним метода + массива для обработки особого случая получения цвета в качестве второго операнда. Однако я признаю, что такой подход довольно уродлив.

class Array
  alias color_plus +
  def +(b)
    if b.is_a?(Color)
      return b+self
    end
    return color_plus(b)
  end
end
1 голос
/ 07 июля 2012

Продолжая работу над ответом @ 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

Одна из задач состояла в том, чтобы убедиться, что инвертированный вызов метода 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#*.

...