从数组强制

发布于 2024-12-06 07:56:51 字数 717 浏览 0 评论 0原文

假设我有这个简单的类:

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

定义强制不起作用。我怎样才能让它发挥作用?

Suppose I have this simple class:

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

I know this is a bad way to implement it but this is the shortest way I can think.

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

It also works if I give an Array as Colors:

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

But I can't to this:

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

Defining coerce doesn't work. How can I make it working?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

最笨的告白 2024-12-13 07:56:51

这是因为代码

[50, 50, 50] + c100

调用 Array 上的 + 方法,而不是 Color,并且该方法无法将颜色转换为 Array。

相比之下,

 c100 + [50, 50, 50]

确实调用 Color 的 + 方法。

然而,即使你在 Color:

class Color
def to_ary
return @rgb
end
end

Array 方法中定义了转换方法,也不会像你期望的那样工作;结果将是两个数组的串联,因为 Array 的 + 方法连接它们的操作数,而不是添加它们的元素:

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

这里,结果将是一个 Array,而不是 Color。

编辑:

我认为实现这项工作的唯一方法是为 Array 的 + 方法起别名,以处理接收 Color 作为第二个操作数的特殊情况。然而,我承认这种方法相当丑陋。

class Array
  alias color_plus +
  def +(b)
    if b.is_a?(Color)
      return b+self
    end
    return color_plus(b)
  end
end

It's because the code

[50, 50, 50] + c100

calls the + method on Array, not Color, and that method can't convert a color to an Array.

By contrast,

 c100 + [50, 50, 50]

does call Color's + method.

However, even if you define a conversion method in Color:

class Color
def to_ary
return @rgb
end
end

the Array method will not work as you expect; the result will be the concatenation of the two arrays, since Array's + method concatenates their operands, rather than adding their elements:

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

Here, the result will be an Array, rather than a Color.

EDIT:

The only way I see of making this work is to alias the + method of Array to handle the special case of receiving a Color as the second operand. However, I will admit that this approach is rather ugly.

class Array
  alias color_plus +
  def +(b)
    if b.is_a?(Color)
      return b+self
    end
    return color_plus(b)
  end
end
强者自强 2024-12-13 07:56:51

进一步阐述@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#- 和 <代码>数组#*。

Elaborating further on @peter-o answer I came up with this implementation that, although uggly in the sense that it redefines several methods of Array, it pretty much manages to be a good workaround for the expected behaviour, I don't think I would ever fit this in production code but I really liked the challenge... Sorry for diverging on the color subject but I didn't know what the expected behaviour for minus and times would be.

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

One of the chalenges was to make sure the inverted call on the Point#- method would not return unexpected results, hence the @coerced instance variable as a control flag on the object.

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

After all, what this code manages to achieve is adding coerce functionality to the Array class where it didn't exist, explicitly to methods Array#+, Array#- and Array#*.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文